-module(rtps_psm).

%%% Some code derived from orber cdrlib Copyright Ericsson AB 1997-2009.

%%% 9 Platform Specific Model (PSM) : UDP/IP.  This module more
%%% or less covers all material from chapter 9, the Platform Specific
%%% Model, except for the actual sending and recieving of UDP
%%% data. The UDP/IP only revers to the addtion of the udpv4 locator
%%% message to the set of standard messages implemented in this
%%% module.
%%% NOTE CDR encoding is always big endian!! 
%%% TODO: implement fragmentation.
%%% TODO: Optimize bit manipulation.
%%% TODO: In the tests, match with byte sequences and not N:M
%%% bitstrings to make sure the bytes order on the wire uses the
%%% proper sequence.

%% -include_lib("kernel/include/logger.hrl").
-include_lib("eunit/include/eunit.hrl").
-include("rtps.hrl").
-include("rtps_psm.hrl").
-include("rtps_udp.hrl").

-ifdef(TEST).
  -compile(export_all).
-endif.

-export([to_sequence_number_set/2, to_fragment_number_set/2]).

-export([enc_header/1, dec_header/1,
	 enc_submsg/1, dec_submsgs/1,
	 enc_message/2, dec_message/1, 
	 endianness/0,
	 size/1, size/2,
	 enc_parl/1,
	 enc_pmd/1, dec_pmd/1,
	 enc_data/1, dec_data/1]).

%% RTPS 2.2
%% -export([
%% 	 enc_guid/1, dec_guid/1,
%% 	 enc_guid_prefix/2, dec_guid_prefix/1,
%% 	 enc_entity_id/2, dec_entity_id/1,
%% 	 enc_vendor/2, dec_vendor/1,
%% 	 enc_prot_ver/2, dec_prot_ver/1,
%% 	 enc_sn/2, dec_sn/2,
%% 	 enc_snset/2, dec_snset/2,
%% 	 enc_fn/2, dec_fn/2,
%% 	 enc_fnset/2, dec_fnset/2,
%% 	 enc_ts/2, dec_ts/2,
%% 	 enc_parl/1, enc_parl/2, dec_parl/2,
%% 	 enc_count/2, dec_count/2,
%% 	 enc_locl/2, dec_locl/2,
%% 	 enc_loc/2, dec_loc/2,
%% 	 enc_locudpv4/2, dec_locudpv4/2,
%% 	 enc_duration/1, enc_duration/2, dec_duration/2
%% 	]).

%% 	 %% IIOP 1.0 -
%% -export([enc_short/1, enc_short/2, dec_short/2,
%% 	 enc_unsigned_short/1, enc_unsigned_short/2, dec_unsigned_short/2,
%% 	 enc_long/1, enc_long/2, dec_long/2,
%% 	 enc_unsigned_long/1, enc_unsigned_long/2, dec_unsigned_long/2,
%% 	 enc_bool/1, enc_bool/2, dec_bool/1,
%% 	 enc_float/1, enc_float/2, dec_float/2,
%% 	 enc_double/1, enc_double/2, dec_double/2,
%% 	 enc_char/1, enc_char/2, dec_char/1,
%% 	 enc_octet/1, enc_octet/2, dec_octet/1,
%% 	 enc_string/1, enc_string/2, dec_string/2,
%% 	 enc_enum/2, enc_enum/3, dec_enum/3,
%% 	 enc_sequence/1, enc_sequence/2, enc_sequence/3, dec_sequence/2, dec_sequence/3,
%% 	 %% IIOP 1.1 - 
%% 	 enc_longlong/1, enc_longlong/2, dec_longlong/2,
%% 	 enc_unsigned_longlong/1, enc_unsigned_longlong/2, dec_unsigned_longlong/2
%% 	]).

%%%===================================================================
%%% Types
%%%===================================================================
-export_type([header/0, message/0, submsg/0, header_r/0, message_r/0, submsg_r/0]).
-type header() :: #header{}.
-type message() :: #message{}.
-type submsg() :: #submsg{}.
-type header_r() :: <<_:160>>.
-type message_r() :: <<_:32,  _:_*32>>.
-type submsg_r() :: <<_:32,  _:_*32>>.

%%%===================================================================
%%% Defines and module records
%%%===================================================================

%% TODO: How should the Endianess flag be set by default?
-define(E, 0). % endianness flag set to big for now.

%% MTU is defined in rtps.hrl for example. It is transport dependent.
-ifndef(MTU).
  -define(MTU, ?MTU_MAX).
-endif.

%%%===================================================================
%%% API
%%%===================================================================

-spec to_sequence_number_set(Base, List) -> {rtps:sequence_number_set(), Rest} when
      Base :: rtps:sequence_number(),
      List :: [rtps:sequence_number()],
      Rest :: [rtps:sequence_number()].
%% @doc Turn a list of sequence numbers into a number set. As far as
%% the sequence numbers are within the maximum allowd range, sequence
%% numbers from the 'List' are added to the resulting set. The size of
%% the resulting set depends on the values of the sequenece in the
%% 'List'.
%%
%% The first number in the list should be greater or equal to the
%% Base. There is no error checking because these parameters are set
%% within the program and not externally. If the numbers in the list
%% exceed the maximum range a sequence number can represent, the
%% remaining numbers from the list are returned as the Rest. NB:
%% sequence numbers are always greater or equal than 1.
%%
%% TODO: Review this code! 
to_sequence_number_set(Base, List) ->
    {Set, N, Rest} = to_number_set(Base, List),
    {#sequence_number_set{base = Base, set = Set, num_bits = N}, Rest}.

-spec to_fragment_number_set(Base, List) -> {rtps:fragment_number_set(), Rest} when
      Base :: rtps:sequence_number(),
      List :: [rtps:fragment_number()],
      Rest :: [rtps:fragment_number()].
%% @doc Turn a list of fragment numbers into a list of fragment number
%% sets. Fragment numbers are in the range 0..16#ffffffff (a 32 bit unsigned integer).
to_fragment_number_set(Base, List) ->
    {Set, N, Rest} = to_number_set(Base, List),
    {#fragment_number_set{base = Base, set = Set, num_bits = N}, Rest}.

-spec enc_message(header_r(), nonempty_list(submsg())) -> message_r().
%% @doc Encode the final message by taking the binary header and the
%% list of encoded submessages, resulting in the binary representation
%% of the RTPS message according to 9.4.1 Overall Structure (p159).
enc_message(Header, Submessages)
  when Submessages =/= [] ->
    <<Header/binary, (enc_submsgs(Submessages, <<>>))/binary>>.

-spec dec_message(message_r()) -> message() | undefined.
%% doc Reverse of message/1.
%% @see message/1.
dec_message(<<Bin_h:20/bytes, Bin_s/binary>>) ->
    case dec_header(Bin_h) of
	Header when is_record(Header, header) ->
	    Submessages = dec_submsgs(Bin_s),
	    #message{header = Header, submessages = Submessages};
	undefined ->
	    undefined
    end.

-spec size(msg_header | submsg_header) -> Size :: non_neg_integer().
%% @doc Returns the size in bytes of the rtps message header,
%% submessage header or the maximum size of the total message,
%% including the header.
size(msg_header) ->
    ?RTPS_MSG_HEADER_SIZE;
size(submsg_header) ->
    ?RTPS_SUBMSG_HEADER_SIZE.

-spec size(max_msg, MTU :: non_neg_integer()) -> Size :: non_neg_integer().
%% TODO: what value to use for minimal MTU: is something with minimal
%% fragment size plus overhead.
size(max_msg, MTU) when MTU > ?MTU_MAX->
    size(max_msg, ?MTU_MAX);
size(max_msg, MTU) ->
    MTU - ?UDP_HEADER_SIZE - ?IP_HEADER_SIZE.


%%%===================================================================
%%% Internal 
%%%===================================================================

%% @private
%% TODO: Instead of 1, increment by 32 because the bitmap is
%% always a multiple of 32 bits? The encode sequence number function
%% will take care of proper aligning but by using 32 instead of 1 we
%% may be more inline with other implementations and a little more
%% effective???
to_number_set(_Base, []) ->
    {[], 1, []};
to_number_set(Base, List) ->
    to_number_set(1, Base, List, []).
    
%% @private
%% N is the required number of bits to represent the
%% resulting set with a maximum of ?MAX_NS_SIZE (typically 256) which
%% is PSM dependent. (When incrementing by 32 instead of 1, replace
%% the 1 below by 32).
to_number_set(N, _Base, [], Set) ->
    {lists:reverse(Set), N, []};
to_number_set(N, Base, [X | Rest], Set)
  when X - Base < N ->
    to_number_set(N, Base, Rest, [X | Set]);
to_number_set(N, Base, [X | _] = Rest, Set)
  when X - Base < ?MAX_NS_SIZE ->
    to_number_set(N + 1, Base, Rest, Set);
to_number_set(N, _Base, Rest, Set) ->
    {lists:reverse(Set), N, Rest}.

%%= I n t e r n a l  t o  w i r e ====================================

enc_submsgs([], Bin) ->
    Bin;
enc_submsgs([#submsg{id = Id, flags = Flags, length = Length, content = Content} | Submessages], Bin) ->
    Length16 = case Length of
		   %% Mesages > 64K must be last submessage
		   _ when Length > 16#ffff, Submessages =:= [] ->
		       0;
		   _ when Length =< 16#ffff ->
		       Length
	       end,
    Bin1 = enc_unsigned_short(Length16, <<Id, Flags>>),
    enc_submsgs(Submessages, <<Bin/binary, Bin1/binary, Content/binary>>).

-spec enc_header(header()) -> header_r().
%% @doc Transform from internal reperesentation to external binary data.
%% 9.4.4 Mapping of the RTPS Header (p167).
enc_header(#header{protocol_version = Protocol_version, vendor_id = Vendor_id, 
		   guid_prefix = Guid_prefix}) ->
    Bin1 = enc_prot_ver(Protocol_version, <<"RTPS">>),
    Bin2 = enc_vendor(Vendor_id, Bin1),
    enc_guid_prefix(Guid_prefix, Bin2).

-spec enc_submsg(rtps:submessage()) -> submsg().
%% @doc Map internal submessage representation into binary external data.
%% 9.4.5 Mapping of the RTPS Submessages (p168).

%% 9.4.5.2 AckNack Submessage p169
enc_submsg(#acknack{final_flag = Final_flag, reader_id = Reader_id, writer_id = Writer_id,
		    reader_sn_state = Reader_sn_state, count = Count}) ->
    F = case Final_flag of true -> ?E bor 2; false -> ?E end,
    C0 = enc_entity_id(Reader_id, <<>>),
    C1 = enc_entity_id(Writer_id, C0),
    C2 = enc_snset(Reader_sn_state, C1),
    C3 = enc_count(Count, C2),
    L = byte_size(C3),
    #submsg{id = ?ACKNACK, flags = F, length = L, content = C3};
%% @doc 9.4.5.3 Data Submessage p170.
%%
%% TODO: how to handle zero length data or keys? Currently zero length
%% data and keys are excepted and a zero length field is inserted for
%% the serialized data or key. The D or K flag is set. The issue is
%% academic since the serialization will probably make these fileds
%% non-zero length anyhow.
enc_submsg(#data_msg{extra_flags = Extra_flags, reader_id = Reader_id, 
		     writer_id = Writer_id, writer_sn = Writer_sn, 
		     inline_qos = Inline_qos, serialized_key = Serialized_key, 
		     serialized_data = Serialized_data}) ->
    C0 = case Extra_flags of
	     undefined ->
		 <<0, 0>>;
	     _ ->
		 Extra_flags
	 end,
    C1 = enc_entity_id(Reader_id, <<>>),
    C2 = enc_entity_id(Writer_id, C1),
    C3 = enc_sn(Writer_sn, C2),
    %% Calculate octetsToInlineQos, append to exta flags
    C4 = enc_unsigned_short(byte_size(C3), C0),
    C5 = <<C4/binary, C3/binary>>,

    F0 = ?E,
    {F1, L0, C6} = case Inline_qos of 
		       undefined ->
			   %% No parameter list
			   {F0, byte_size(C5), C5};
		       _ ->
			   %% TODO: is the guard needed?
			   C_qos = enc_parl(Inline_qos, C5),
 			   {F0 bor 2, byte_size(C_qos), C_qos}
		   end,
    {F2, Payload} = case {Serialized_data, Serialized_key} of
			{undefined, undefined} -> 
			    {F1, undefined};
			{_, undefined} ->
			    {F1 bor 4, Serialized_data};
			{undefined, _} ->
			    {F1 bor 8, Serialized_key}
	    end,
    {L1, C7} = case Payload of
		   undefined ->
		       {L0, C6};
		   _ ->
		       {Padded_payload, Size, Padding} = pad32(Payload),
		       {L0 + Size + Padding, <<C6/binary, Padded_payload/binary>>}
	       end,
    #submsg{id = ?DATA, flags = F2, length = L1, content = C7};
%% @doc 9.4.5.4 DataFrag Submessage p171 
enc_submsg(#data_frag{key_flag = Key_flag, extra_flags = Extra_flags, 
		      reader_id = Reader_id, writer_id = Writer_id, writer_sn = Writer_sn,
		      fragment_starting_num = Fragment_starting_num, 
		      fragments_in_submessage = Fragments_in_submessage, 
		      sample_size = Sample_size, inline_qos = Inline_qos, 
		      serialized_payload = Serialized_payload}) -> 
    C0 = case Extra_flags of
	     undefined ->
		 <<0, 0>>;
	     _ ->
		 Extra_flags
	 end,
    C1 = enc_entity_id(Reader_id, <<>>),
    C2 = enc_entity_id(Writer_id, C1),
    C3 = enc_sn(Writer_sn, C2),
    C4 = enc_fn(Fragment_starting_num, C3),
    C5 = enc_unsigned_short(Fragments_in_submessage, C4),
    C6 = enc_unsigned_short(byte_size(Serialized_payload), C5), % fragmentSize
    C7 = enc_unsigned_long(Sample_size, C6),
    C8 = enc_unsigned_short(byte_size(C7), C0),
    C9 = <<C8/binary, C7/binary>>,

    F = ?E,
    F0 = case Key_flag of true -> F bor 4; false -> F end,
    {F1, L0, C10} = case Inline_qos of 
			undefined ->
			    {F0, byte_size(C9), C9};
			_ ->
			    %% TODO: is the guard needed?
			    C_qos = enc_parl(Inline_qos, C9),
			    {F0 bor 2, byte_size(C_qos), C_qos}
		    end,
    {Padded_payload, Size, Padding} = pad32(Serialized_payload),
    L1 = L0 + Size + Padding,
    C11 = <<C10/binary, Padded_payload/binary>>,
    #submsg{id = ?DATA_FRAG, flags = F1, length = L1, content = C11};
%% @doc 9.4.5.5 Gap Submessage p172.
enc_submsg(#gap{reader_id = Reader_id, writer_id = Writer_id, gap_start = Gap_start, 
		gap_list = Gap_list}) ->
    C0 = enc_entity_id(Reader_id, <<>>),
    C1 = enc_entity_id(Writer_id, C0),
    C2 = enc_sn(Gap_start, C1),
    C3 = enc_snset(Gap_list, C2),
    F = ?E,
    L = byte_size(C3),
    #submsg{id = ?GAP, flags = F, length = L, content = C3};
%% @doc 9.4.5.6 HeartBeat Submessage
enc_submsg(#heartbeat{final_flag = Final_flag, liveliness_flag = Liveliness_flag, 
		      reader_id = Reader_id, writer_id = Writer_id, 
		      first_sn = First_sn, last_sn = Last_sn, count = Count}) ->
    C0 = enc_entity_id(Reader_id, <<>>),
    C1 = enc_entity_id(Writer_id, C0),
    C2 = enc_sn(First_sn, C1),
    C3 = enc_sn(Last_sn, C2),
    C4 = enc_count(Count, C3),
    F = ?E,
    F0 = case Final_flag of true -> F bor 2; false -> F end,
    F1 = case Liveliness_flag of true -> F0 bor 4; false -> F0 end,
    L = byte_size(C4),
    #submsg{id = ?HEARTBEAT, flags = F1, length = L, content = C4};
%% @doc 9.4.5.7 HeartBeatFrag Submessage
enc_submsg(#heartbeat_frag{reader_id = Reader_id, writer_id = Writer_id, 
			   writer_sn = Writer_sn, last_fragment_num = Last_fragment_num, 
			   count = Count}) ->
    C0 = enc_entity_id(Reader_id, <<>>),
    C1 = enc_entity_id(Writer_id, C0),
    C2 = enc_sn(Writer_sn, C1),
    C3 = enc_fn(Last_fragment_num, C2),
    C4 = enc_count(Count, C3),
    F = ?E,
    L = byte_size(C4),
    #submsg{id = ?HEARTBEAT_FRAG, flags = F, length = L, content = C4};
%% @doc 9.4.5.8 InfoDestination Submessage
enc_submsg(#info_destination{guid_prefix = Guid_prefix}) ->
    F = ?E,
    C = enc_guid_prefix(Guid_prefix, <<>>),
    L = byte_size(C),
    #submsg{id = ?INFO_DST, flags = F, length = L, content = C};
%% @doc 9.4.5.9 InfoReply Submessage
enc_submsg(#info_reply{unicast_locator_list = Unicast_locator_list, 
		       multicast_locator_list = Multicast_locator_list}) ->
    F = ?E,
    C = enc_locl(Unicast_locator_list, <<>>),
    {F0, C0} = case Multicast_locator_list of
		   undefined ->
		       {F, C};
		   _ ->
		       {F bor 2, enc_locl(Multicast_locator_list, C)}
	       end,
    L = byte_size(C0),
    #submsg{id = ?INFO_REPLY, flags = F0, length = L, content = C0};
%% @doc 9.4.5.14 InfoReplyIp4 Submessage (PSM specific)
enc_submsg(#info_reply_ip4{unicast_locator = Unicast_locator, 
			   multicast_locator = Multicast_locator}) ->
    F = ?E,
    C = enc_locudpv4(Unicast_locator, <<>>),
    {F0, C0} = case Multicast_locator of
		   undefined ->
		       {F, C};
		   _ ->
		       {F bor 2, enc_locudpv4(Multicast_locator, C)}
	       end,
    L = byte_size(C0),
    #submsg{id = ?INFO_REPLY_IP4, flags = F0, length = L, content = C0};
%% @doc 9.4.5.10 InfoSource Submessage
enc_submsg(#info_source{protocol_version = Version, vendor_id = Vendor_id, 
			guid_prefix = Guid_prefix}) ->
    F = ?E,
    C0 = enc_prot_ver(Version, <<0:32>>),
    C1 = enc_vendor(Vendor_id, C0),
    C2 = enc_guid_prefix(Guid_prefix, C1),
    L = byte_size(C2),
    #submsg{id = ?INFO_SRC, flags = F, length = L, content = C2};
%% @doc 9.4.5.11 InfoTimestamp Submessage
enc_submsg(#info_timestamp{timestamp = Timestamp}) ->
    F = ?E,
    {F0, C} = case Timestamp of 
		  undefined -> 
		      {F bor 2, <<>>}; 
		  _ -> 
		      {F, enc_ts(Timestamp, <<>>)}
	      end,
    L = byte_size(C),
    #submsg{id = ?INFO_TS, flags = F0, length = L, content = C};
%% @doc 9.4.5.13 NackFrag Submessage
enc_submsg(#nack_frag{reader_id = Reader_id, writer_id = Writer_id, writer_sn = Writer_sn, 
		      fragment_number_state = Fragment_number_state, count = Count}) ->
    C0 = enc_entity_id(Reader_id, <<>>),
    C1 = enc_entity_id(Writer_id, C0),
    C2 = enc_sn(Writer_sn, C1),
    C3 = enc_fnset(Fragment_number_state, C2),
    C4 = enc_count(Count, C3),
    F = ?E,
    L = byte_size(C4),
    #submsg{id = ?NACK_FRAG, flags = F, length = L, content = C4};
enc_submsg(#pad{}) ->
    #submsg{id = ?PAD, flags = ?E, length = 0, content = <<>>}.


%%= W i r e  t o  i n t e r n a l ====================================

-spec dec_header(header_r()) -> header() | undefined.
%% @doc external binary data to internal representation.
%% @see header/1.
dec_header(<<"RTPS", Bin/binary>>) ->
    {Protocol_version, Rest0} = dec_prot_ver(Bin),
    {Vendor_id, Rest1} = dec_vendor(Rest0),
    {Guid_prefix, _} = dec_guid_prefix(Rest1),
    #header{protocol_version = Protocol_version, vendor_id = Vendor_id, 
	    guid_prefix = Guid_prefix};
dec_header(_) ->
    undefined.

dec_submsgs(Bin) ->
    dec_submsgs(Bin, []).

dec_submsgs(<<>>, Submessages) ->
    lists:reverse(Submessages);
dec_submsgs(<<Id, Flags:1/bytes, Bin/binary>>, Submessages) ->
    Endianness = case Flags of
		    <<_:7, 0:1>> -> big;
		    <<_:7, 1:1>> -> little
		end,
    {Length, Bin0} = dec_unsigned_short(Endianness, Bin),
    %% See 9.4.5.1.3 octetsToNextHeader: In case octetsToNextHeader==0
    %% and the kind of Submessage is NOT PAD or INFO_TS, the
    %% Submessage is the last Submessage in the Message and extends up
    %% to the end of the Message.
    Real_length = case Length of
		      0 when not(Id =:= ?PAD orelse Id =:= ?INFO_TS) ->
			  byte_size(Bin0);
		      _ ->
			  Length
		  end,
    <<Bin1:Real_length/bytes, Rest/binary>> = Bin0,
    case dec_submsg(Id, Endianness, Flags, Bin1) of
    	undefined ->
    	    dec_submsgs(Rest, Submessages);
    	Submessage ->
    	    dec_submsgs(Rest, [Submessage | Submessages])
    end.
    %% try
    %% 	<<Bin1:Real_length/bytes, Rest/binary>> = Bin0,
    %% 	case dec_submsg(Id, Endianness, Flags, Bin1) of
    %% 	    undefined ->
    %% 		dec_submsgs(Rest, Submessages);
    %% 	    Submessage ->
    %% 		dec_submsgs(Rest, [Submessage | Submessages])
    %% 	end
    %% catch
    %% 	error:{badmatch, _} ->
    %% 	    ?ERROR_MSG("Truncated message detected:"),
    %% 	    dec_submsgs(<<>>, Submessages)
    %% end.

-spec dec_submsg(Id, Endianness, Flags, Bin) -> rtps:submessage() | undefined when
      Id :: byte(),
      Endianness :: big | little,
      Flags ::<<_:8>>,
      Bin :: binary().
%% @doc external binary data to internal representation.
%% @see submessage/1.
dec_submsg(?ACKNACK, Endianness, <<_:6, F:1, _E:1>>, Bin) ->
    Final_flag = (F =:= 1),
    {Reader_id, Rest0} = dec_entity_id(Bin),
    {Writer_id, Rest1} = dec_entity_id(Rest0),
    {Reader_sn_state, Rest2} = dec_snset(Endianness, Rest1),
    {Count, _} = dec_count(Endianness, Rest2),
    #acknack{final_flag = Final_flag, reader_id = Reader_id, writer_id = Writer_id, 
	     reader_sn_state = Reader_sn_state, count = Count};
dec_submsg(?DATA, Endianness, <<_:4, K:1, D:1, Q:1, _E:1>>, Bin) ->
    <<Extra_flags:2/bytes, Rest0/binary>> = Bin,
    {Octets_to_inline_qos, Rest1} = dec_unsigned_short(Endianness, Rest0),
    {Reader_id, Rest2} = dec_entity_id(Rest1),
    {Writer_id, Rest3} = dec_entity_id(Rest2),
    {Writer_sn, _} = dec_sn(Endianness, Rest3),
    
    <<_:Octets_to_inline_qos/bytes, Bin1/binary>> = Rest1,
    {Inline_qos, Payload} = case Q of
				1 -> dec_parl(Endianness, Bin1);
				0 -> {undefined, Bin1}
			    end,
    case {D, K} of
	{0, 0} ->
	    Serialized_data = Serialized_key = undefined;
	{1, 0} ->
	    Serialized_data = Payload,
	    Serialized_key = undefined;
	{0, 1} ->
	    Serialized_data = undefined,
	    Serialized_key = Payload
     end,
    #data_msg{extra_flags = Extra_flags, reader_id = Reader_id, writer_id = Writer_id, 
	      writer_sn = Writer_sn, inline_qos = Inline_qos, serialized_key = Serialized_key,
	      serialized_data = Serialized_data, endianness = Endianness};
dec_submsg(?DATA_FRAG, Endianness, <<_:5, K:1, Q:1, _E:1>>, Bin) ->
    Key_flag = (K =:= 1),
    <<Extra_flags:2/bytes, Rest0/binary>> = Bin,
    {Octets_to_inline_qos, Rest1} = dec_unsigned_short(Endianness, Rest0),
    {Reader_id, Rest2} = dec_entity_id(Rest1),
    {Writer_id, Rest3} = dec_entity_id(Rest2),
    {Writer_sn, Rest4} = dec_sn(Endianness, Rest3),
    {Fragment_starting_num, Rest5} = dec_fn(Endianness, Rest4),
    {Fragments_in_submessage, Rest6} = dec_unsigned_short(Endianness, Rest5),
    {Fragment_size, Rest7} = dec_unsigned_short(Endianness, Rest6),
    {Sample_size, _} = dec_unsigned_long(Endianness, Rest7),

    <<_:Octets_to_inline_qos/bytes, Bin1/binary>> = Rest1,
    {Inline_qos, Payload} = case Q of
				1 -> dec_parl(Endianness, Bin1);
				0 -> {undefined, Bin1}
			    end,
    <<Serialized_payload:Fragment_size/bytes, _/binary>> = Payload,
    #data_frag{key_flag = Key_flag, extra_flags = Extra_flags, reader_id = Reader_id, 
	       writer_id = Writer_id, writer_sn = Writer_sn,
	       fragment_starting_num = Fragment_starting_num, 
	       fragments_in_submessage = Fragments_in_submessage, 
	       sample_size = Sample_size, inline_qos = Inline_qos, 
	       serialized_payload = Serialized_payload, endianness = Endianness};
dec_submsg(?GAP, Endianness, <<_:7, _E:1>>, Bin) ->
    {Reader_id, Rest0} = dec_entity_id(Bin),
    {Writer_id, Rest1} = dec_entity_id(Rest0),
    {Gap_start, Rest2} = dec_sn(Endianness, Rest1),
    {Gap_list, _} = dec_snset(Endianness, Rest2),
    #gap{reader_id = Reader_id, writer_id = Writer_id, gap_start = Gap_start,
	 gap_list = Gap_list};
dec_submsg(?HEARTBEAT, Endianness, <<_:5, L:1, F:1, _E:1>>, Bin) ->
    Final_flag = (F =:= 1),
    Liveliness_flag = (L =:= 1),
    {Reader_id, Rest0} = dec_entity_id(Bin),
    {Writer_id, Rest1} = dec_entity_id(Rest0),
    {First_sn, Rest2} = dec_sn(Endianness, Rest1),
    {Last_sn, Rest3} = dec_sn(Endianness, Rest2),
    {Count, _} = dec_count(Endianness, Rest3),
    #heartbeat{final_flag = Final_flag, liveliness_flag = Liveliness_flag, 
	       reader_id = Reader_id, writer_id = Writer_id, first_sn = First_sn,
	       last_sn = Last_sn, count = Count};
dec_submsg(?HEARTBEAT_FRAG, Endianness, <<_:7, _E:1>>, Bin) ->
    {Reader_id, Rest0} = dec_entity_id(Bin),
    {Writer_id, Rest1} = dec_entity_id(Rest0),
    {Writer_sn, Rest2} = dec_sn(Endianness, Rest1),
    {Last_fragment_num, Rest3} = dec_fn(Endianness, Rest2),
    {Count, _} = dec_count(Endianness, Rest3),
    #heartbeat_frag{reader_id = Reader_id, writer_id = Writer_id, writer_sn = Writer_sn, 
		    last_fragment_num = Last_fragment_num, count = Count};
dec_submsg(?INFO_DST, _Endianness, <<_:7, _E:1>>, Bin) ->
    {Guid_prefix, _} = dec_guid_prefix(Bin),
    #info_destination{guid_prefix = Guid_prefix};
dec_submsg(?INFO_REPLY, Endianness, <<_:6, M:1, _E:1>>, Bin) ->
    {Unicast_locator_list, Rest0} = dec_locl(Endianness, Bin),
    {Multicast_locator_list, _} = case M of
				 0 -> {[], <<>>};
				 1 -> dec_locl(Endianness, Rest0)
			     end,
    #info_reply{unicast_locator_list = Unicast_locator_list,
		multicast_locator_list = Multicast_locator_list};
dec_submsg(?INFO_REPLY_IP4, Endianness, <<_:6, M:1, _E:1>>, Bin) ->
    {Unicast_locator, Rest0} = dec_locudpv4(Endianness, Bin),
    {Multicast_locator, _} = case M of
				 0 -> {undefined, <<>>};
				 1 -> dec_locudpv4(Endianness, Rest0)
			     end,
    #info_reply_ip4{unicast_locator = Unicast_locator, 
		    multicast_locator =  Multicast_locator};
dec_submsg(?INFO_SRC,  Endianness, <<_:7, _E:1>>, Bin) ->
    {_, Rest0} = dec_long(Endianness, Bin),
    {Version, Rest1} = dec_prot_ver(Rest0),
    {Vendor_id, Rest2} = dec_vendor(Rest1),
    {Guid_prefix, _} = dec_guid_prefix(Rest2),
    #info_source{protocol_version = Version, vendor_id = Vendor_id, 
		 guid_prefix = Guid_prefix};
dec_submsg(?INFO_TS, Endianness, <<_:6, 0:1, _E:1>>, Bin) ->
    {Timestamp, _} = dec_ts(Endianness, Bin),
    #info_timestamp{timestamp = Timestamp};
dec_submsg(?INFO_TS, _Endianness, <<_:6, 1:1, _E:1>>, _) ->
    #info_timestamp{timestamp = undefined};
dec_submsg(?NACK_FRAG, Endianness, <<_:7, _E:1>>, Bin) ->
    {Reader_id, Rest0} = dec_entity_id(Bin),
    {Writer_id, Rest1} = dec_entity_id(Rest0),
    {Writer_sn, Rest2} = dec_sn(Endianness, Rest1),
    {Fragment_number_state, Rest3} = dec_fnset(Endianness, Rest2),
    {Count, _} = dec_count(Endianness, Rest3),
    #nack_frag{reader_id = Reader_id, writer_id = Writer_id, writer_sn = Writer_sn, 
	       fragment_number_state = Fragment_number_state, count = Count};
dec_submsg(?PAD, _Endianness, _Flags, _Bin) ->
    #pad{};
dec_submsg(_Submessage_id, _Endianness, _Flags, _Bin) ->
    %% A Submessage with an unknown SubmessageId must be ignored and
    %% parsing must continue with the next Submessage. Concretely: an
    %% implementation of RTPS 2.2 must ignore any Submessages with IDs
    %% that are outside of the SubmessageKind set defined in version
    %% 2.2. SubmessageIds in the vendor-specific range coming from a
    %% vendorId that is unknown must also be ignored and parsing must
    %% continue with the next Submessage.
    undefined.



%% Test for endianness:
endianness() when <<1:4/native-unit:8>> =:= <<1:4/big-unit:8>> ->
    big;
endianness() ->
    little.



%% @doc 8.3.5.1 / 9.4.2.1 GuidPrefix
enc_guid_prefix(unknown) ->
    <<0:12/big-unit:8>>;
enc_guid_prefix(Guid_prefix)
  when is_binary(Guid_prefix) andalso byte_size(Guid_prefix) =:= 12 ->
    <<Guid_prefix/binary>>.

enc_guid_prefix(Guid_prefix, Bin) ->
    <<Bin/binary, (enc_guid_prefix(Guid_prefix))/binary>>.

%% enc_guid_prefix(unknown, Bin) ->
%%     <<Bin/binary, 0:12/big-unit:8>>;
%% enc_guid_prefix(Guid_prefix, Bin)
%%   when is_binary(Guid_prefix) andalso byte_size(Guid_prefix) =:= 12 ->
%%     <<Bin/binary, Guid_prefix/binary>>.
    
%% @doc 8.3.5.1 / 9.4.2.1 GuidPrefix
dec_guid_prefix(<<0:96, Rest/binary>>) ->
    {unknown, Rest};
dec_guid_prefix(<<Guid_prefix:12/bytes, Rest/binary>>) ->
    {Guid_prefix, Rest}.

%% @doc 8.3.5.1 / 9.4.2.1 EntityId
enc_entity_id(participant) ->
    enc_entity_id(#entity_id{type = built_in, kind = participant, key = <<0,0,1>>});
enc_entity_id('SEDPbuiltinTopicWriter') -> 
    enc_entity_id(#entity_id{type = built_in, kind = writer_with_key, key = <<0,0,2>>});
enc_entity_id('SEDPbuiltinTopicReader') ->
    enc_entity_id(#entity_id{type = built_in, kind = reader_with_key, key = <<0,0,2>>});
enc_entity_id('SEDPbuiltinPublicationsWriter') ->
    enc_entity_id(#entity_id{type = built_in, kind = writer_with_key, key = <<0,0,3>>});
enc_entity_id('SEDPbuiltinPublicationsReader') ->
    enc_entity_id(#entity_id{type = built_in, kind = reader_with_key, key = <<0,0,3>>});
enc_entity_id('SEDPbuiltinSubscriptionsWriter') ->
    enc_entity_id(#entity_id{type = built_in, kind = writer_with_key, key = <<0,0,4>>});
enc_entity_id('SEDPbuiltinSubscriptionsReader') ->
    enc_entity_id(#entity_id{type = built_in, kind = reader_with_key, key = <<0,0,4>>});
enc_entity_id('SPDPbuiltinParticipantWriter') ->
    enc_entity_id(#entity_id{type = built_in, kind = writer_with_key, key = <<0,1,0>>});
enc_entity_id('SPDPbuiltinParticipantReader') ->
    enc_entity_id(#entity_id{type = built_in, kind = reader_with_key, key = <<0,1,0>>});
enc_entity_id('BuiltinParticipantMessageWriter') ->
    enc_entity_id(#entity_id{type = built_in, kind = writer_with_key, key = <<0,2,0>>});
enc_entity_id('BuiltinParticipantMessageReader') ->
    enc_entity_id(#entity_id{type = built_in, kind = reader_with_key, key = <<0,2,0>>});
enc_entity_id(unknown) ->
    <<0:4/big-unit:8>>;
enc_entity_id(#entity_id{type = Type, kind = Kind, key = Key}) 
  when is_binary(Key) andalso byte_size(Key) =:= 3 ->
    Type_b = case Type of
		 user -> ?ENTITY_TYPE_USER;
		 built_in -> ?ENTITY_TYPE_BUILT_IN;
		 vendor -> ?ENTITY_TYPE_VENDOR
	     end,
    %% TODO: remove guard: much to defensive?
    Kind_b = case Kind of
		 _ when Type =:= vendor -> Kind;
		 unknown -> ?ENTITY_KIND_UNKNOWN;
 		 participant when Type =/= user -> ?ENTITY_KIND_PARTICIPANT;
		 writer_with_key -> ?ENTITY_KIND_WRITER_WITH_KEY;
		 writer_no_key -> ?ENTITY_KIND_WRITER_NO_KEY;
		 reader_no_key -> ?ENTITY_KIND_READER_NO_KEY;
		 reader_with_key -> ?ENTITY_KIND_READER_WITH_KEY
	     end,
    <<Key/binary, Type_b:2, Kind_b:6>>.

enc_entity_id(Entity_id, Bin) ->
    <<Bin/binary, (enc_entity_id(Entity_id))/binary>>.

enc_guid(#guid{guid_prefix = Guid_prefix, entity_id = Entity_id}) ->
    <<(enc_guid_prefix(Guid_prefix))/binary, (enc_entity_id(Entity_id))/binary>>.

%% @doc 8.3.5.1 / 9.4.2.1 EntityId
dec_entity_id(<<0, 0, 1, ?ENTITY_TYPE_BUILT_IN:2, ?ENTITY_KIND_PARTICIPANT:6, Rest/binary>>) ->
    {participant, Rest};
dec_entity_id(<<0, 0, 2, ?ENTITY_TYPE_BUILT_IN:2, ?ENTITY_KIND_WRITER_WITH_KEY:6, Rest/binary>>) ->
    {'SEDPbuiltinTopicWriter', Rest};
dec_entity_id(<<0, 0, 2, ?ENTITY_TYPE_BUILT_IN:2, ?ENTITY_KIND_READER_WITH_KEY:6, Rest/binary>>) ->
    {'SEDPbuiltinTopicReader', Rest};
dec_entity_id(<<0, 0, 3, ?ENTITY_TYPE_BUILT_IN:2, ?ENTITY_KIND_WRITER_WITH_KEY:6, Rest/binary>>) ->
    {'SEDPbuiltinPublicationsWriter', Rest};
dec_entity_id(<<0, 0, 3, ?ENTITY_TYPE_BUILT_IN:2, ?ENTITY_KIND_READER_WITH_KEY:6, Rest/binary>>) ->
    {'SEDPbuiltinPublicationsReader', Rest};
dec_entity_id(<<0, 0, 4, ?ENTITY_TYPE_BUILT_IN:2, ?ENTITY_KIND_WRITER_WITH_KEY:6, Rest/binary>>) ->
    {'SEDPbuiltinSubscriptionsWriter', Rest};
dec_entity_id(<<0, 0, 4, ?ENTITY_TYPE_BUILT_IN:2, ?ENTITY_KIND_READER_WITH_KEY:6, Rest/binary>>) ->
    {'SEDPbuiltinSubscriptionsReader', Rest};
dec_entity_id(<<0, 1, 0, ?ENTITY_TYPE_BUILT_IN:2, ?ENTITY_KIND_WRITER_WITH_KEY:6, Rest/binary>>) ->
    {'SPDPbuiltinParticipantWriter', Rest};
dec_entity_id(<<0, 1, 0, ?ENTITY_TYPE_BUILT_IN:2, ?ENTITY_KIND_READER_WITH_KEY:6, Rest/binary>>) ->
    {'SPDPbuiltinParticipantReader', Rest};
dec_entity_id(<<0, 2, 0, ?ENTITY_TYPE_BUILT_IN:2, ?ENTITY_KIND_WRITER_WITH_KEY:6, Rest/binary>>) ->
    {'BuiltinParticipantMessageWriter', Rest};
dec_entity_id(<<0, 2, 0, ?ENTITY_TYPE_BUILT_IN:2, ?ENTITY_KIND_READER_WITH_KEY:6, Rest/binary>>) ->
    {'BuiltinParticipantMessageReader', Rest};
dec_entity_id(<<0, 0, 0, 0, Rest/binary>>) ->
    {unknown, Rest};
dec_entity_id(<<Key:3/bytes, Type_b:2, Kind_b:6, Rest/binary>>) ->
    Type = case Type_b of
	       ?ENTITY_TYPE_USER -> user;
	       ?ENTITY_TYPE_BUILT_IN -> built_in;
	       ?ENTITY_TYPE_VENDOR -> vendor
	   end,
    Kind = case Kind_b of
	       _ when Type =:= vendor -> Kind_b;
	       ?ENTITY_KIND_UNKNOWN -> unknown;
	       ?ENTITY_KIND_PARTICIPANT when Type =/= user -> participant;
	       ?ENTITY_KIND_WRITER_WITH_KEY -> writer_with_key;
	       ?ENTITY_KIND_WRITER_NO_KEY -> writer_no_key;
	       ?ENTITY_KIND_READER_NO_KEY -> reader_no_key;
	       ?ENTITY_KIND_READER_WITH_KEY -> reader_with_key
	   end,
    {#entity_id{type = Type, kind = Kind, key = Key}, Rest}.

dec_guid(<<Bin1:12/bytes, Bin2:4/bytes>>) ->
    {Guid_prefix, _} = dec_guid_prefix(Bin1),
    {Entity_id, _} = dec_entity_id(Bin2),
    #guid{guid_prefix = Guid_prefix, entity_id = Entity_id}.

%% @doc 8.3.5.2 / 9.4.2.3 VendorId
enc_vendor(unknown) ->
    <<0, 0>>;
enc_vendor(Vendor_id)
  when is_binary(Vendor_id), byte_size(Vendor_id) =:= 2 ->
    Vendor_id.

enc_vendor(Vendor_id, Bin) ->
    <<Bin/binary, (enc_vendor(Vendor_id))/binary>>.

%% @doc 8.3.5.2 / 9.4.2.3 VendorId
dec_vendor(<<0, 0, Rest/binary>>) ->
    {unknown, Rest};
dec_vendor(<<Vendor_id:2/bytes, Rest/binary>>) ->
    {Vendor_id, Rest}.

%% @doc 8.3.5.3 / 9.4.2.4 ProtocolVersion
enc_prot_ver(#protocol_version{major = Major, minor = Minor}) ->
    <<Major, Minor>>.

enc_prot_ver(Version, Bin) ->
    <<Bin/binary, (enc_prot_ver(Version))/binary>>.

%% @doc 8.3.5.3 / 9.4.2.4 ProtocolVersion
dec_prot_ver(<<Major, Minor, Rest/binary>>) ->
    {#protocol_version{major = Major, minor = Minor}, Rest}.

%% @doc 8.3.5.5 / 9.4.2.5 SequenceNumber
enc_sn(unknown) ->
    <<255,255,255,255,0,0,0,0>>;
enc_sn(Sequence_nr)
  when Sequence_nr >= 0 ->
    High = Sequence_nr bsr 32, 
    Low = Sequence_nr band 16#ffffffff,
    <<(enc_long(High))/binary, (enc_unsigned_long(Low))/binary>>.

enc_sn(Sequence_nr, Bin) ->
    <<Bin/binary, (enc_sn(Sequence_nr))/binary>>.

%% @doc 8.3.5.5 / 9.4.2.5 SequenceNumber
dec_sn(_Endianness, <<1:1, _:63, Rest/binary>>) ->
    {unknown, Rest};
dec_sn(Endianness, Bin) ->
    {High, Rest1} = dec_long(Endianness, Bin),
    {Low, Rest2} = dec_unsigned_long(Endianness, Rest1),
    {(High bsl 32) + Low, Rest2}. 

%% @doc 8.3.5.5 / 9.4.2.6 SequenceNumberSet p39 / p161: Transform a
%% sequence number set internal representation in the external
%% bitstring.  @see bin_to_sn/1
%%
%% TODO: Change :64 to sn_to_bin().  

%% TODO: *Update SequenceNumberSet definition to explicitly allow
%% 0-bits *. The current definition of the SequenceNumberSet requires
%% at least 1 bit in the bitset but there are use cases where 0 bits
%% are needed. See https://issues.omg.org/issues/DDSIRTP23-37
enc_snset(#sequence_number_set{base = Base, set = Set, num_bits = N}) 
  when Base >= 1, N > 0, N =< 256 ->
    Bin1 = enc_sn(Base),
    Bin2 = enc_unsigned_long(N, Bin1),
    Bitmap = set_to_bitmap(Base, N, Set),
    <<Bin2/binary, Bitmap/binary>>.

enc_snset(SN_set, Bin) ->
    <<Bin/binary, (enc_snset(SN_set))/binary>>.

%% @doc 8.3.5.5 / 9.4.2.6 SequenceNumberSet p39 / p161: transform
%% binary sequence number set into internal representation.  
%% @see snset_to_bin/1
dec_snset(Endianness, Bin) ->
    {Base, Rest1} = dec_sn(Endianness, Bin),
    {N, Rest2} = dec_unsigned_long(Endianness, Rest1),
    M = ((N + 31) div 32) * 4,
    <<Bin1:M/bytes, Rest3/binary>> = Rest2,
    Set = bitmap_to_set(Endianness, Base, N, Bin1),
    {#sequence_number_set{base = Base, set = Set, num_bits = N}, Rest3}.

%% @doc 8.3.5.6 / 9.4.2.7 FragmentNumber
enc_fn(Fragment_nr) ->
    enc_unsigned_long(Fragment_nr).

enc_fn(Fragment_nr, Bin) ->
    <<Bin/binary, (enc_fn(Fragment_nr))/binary>>.

%% @doc 8.3.5.6 / 9.4.2.7 FragmentNumber
dec_fn(Endianness, Bin) ->
    dec_unsigned_long(Endianness, Bin).

%% @doc 8.3.5.7 / 9.4.2.8 FragmentNumberSet p40 / p163: transform
%% binary sequence number set into internal representation.  
%% @see bin_to_fnset/1
%% @see snset_to_bin/1
enc_fnset(#fragment_number_set{base = Base, set = Set, num_bits = N})
  when Base >= 0, N > 0, N =< 256 ->
    Bin1 = enc_fn(Base),
    Bin2 = enc_unsigned_long(N),
    Bitmap = set_to_bitmap(Base, N, Set),
    <<Bin1/binary, Bin2/binary, Bitmap/binary>>.

enc_fnset(FN_set, Bin) ->
    <<Bin/binary, (enc_fnset(FN_set))/binary>>.

%% @doc 8.3.5.7 / 9.4.2.8 FragmentNumberSet p40 / p163: transform
%% binary sequence number set into internal representation.  
%% @see fnset_to_bin/1
dec_fnset(Endianness, Bin) ->
    {Base, Rest1} = dec_fn(Endianness, Bin),
    {N, Rest2} = dec_unsigned_long(Endianness, Rest1),
    M = ((N + 31) div 32) * 4,
    <<Bin1:M/bytes, Rest3/binary>> = Rest2,
    Set = bitmap_to_set(Endianness, Base, N, Bin1),
    {#fragment_number_set{base = Base, set = Set, num_bits = N}, Rest3}.

%% @private
%% TODO set_to_bitmap and bitmap_to_set may need some optimization?
%% TODO: Spec defines the values read to be longs but these should be
%% unsigned longs! p 162
set_to_bitmap(Base, N, Set)
  when 0 < N, N =< 256 ->
    Padding = (32 - (N rem 32)) rem 32,
    Bitmap = <<(set_to_bitmap(Base, N, Set, 0)):N, 0:Padding>>,
    << <<X:32/big-signed-integer>> || <<X:32>> <=  Bitmap>>.

%% @private
set_to_bitmap(_Base, _N, [], X) ->
    X;
set_to_bitmap(Base, N, [H | T], X)
  when Base =< H, H < Base + N ->
    set_to_bitmap(Base, N, T, X bor (1 bsl (N - (H - Base) - 1))).

%% @private
%% TODO: Spec defines the values read to be longs but these should be
%% unsigned longs! p 162

%% Turn external endian dependend bitmap into internal endian independend bitmap
bitmap_to_set(big, Base, N, Bin)
  when 0 < N, N =< 256 ->
    <<Bitmap:N/bitstring, _/bitstring>> = << <<X:32>> || <<X:32/big-signed-integer>> <= Bin>>,
    bitmap_to_set1(Base, N, Bitmap, []);
bitmap_to_set(little, Base, N, Bin)
  when 0 < N, N =< 256 ->
    <<Bitmap:N/bitstring, _/bitstring>> = << <<X:32>> || <<X:32/little-signed-integer>> <= Bin>>,
    bitmap_to_set1(Base, N, Bitmap, []).
%% Turn bitmap plus base into a list of sequence numbers.
bitmap_to_set1(_Base, 0, _Bitmap, Set) ->
    lists:reverse(Set);
bitmap_to_set1(Base, N, <<1:1, Bitmap/bitstring>>, Set) ->
    bitmap_to_set1(Base + 1, N - 1, Bitmap, [Base | Set]);
bitmap_to_set1(Base, N, <<0:1, Bitmap/bitstring>>, Set) ->
    bitmap_to_set1(Base + 1, N - 1, Bitmap, Set).

%% @doc 8.3.5.8 / 9.4.2.9 Timestamp p40 / p164: Translate internal
%% timestamp to external binary representation. The value flag of a
%% timestamp is used to denote the special situations that the
%% timestamp is zero, which is necessary because of the use of a
%% fraction, the timestamp is equal to infinity or invalid.
%% 
%% Table 9.4 - PSM mapping of the value types that appear on the wire
%% p154
enc_time(zero) ->
    <<0:8/big-unit:8>>;
enc_time(invalid) ->
    <<(enc_long(-1))/binary, (enc_unsigned_long(16#ffffffff))/binary>>;
enc_time(infinity) ->
    <<(enc_long(16#7fffffff))/binary, (enc_unsigned_long(16#ffffffff))/binary>>;
enc_time(#time{seconds = Seconds, fraction = Fraction}) ->
    <<(enc_long(Seconds))/binary, (enc_unsigned_long(Fraction))/binary>>.

enc_ts(TS) ->
    <<(enc_time(TS))/binary>>.

enc_ts(TS, Bin) ->
    <<Bin/binary, (enc_ts(TS))/binary>>.

enc_duration(Duration) ->
    <<(enc_time(Duration))/binary>>.

enc_duration(Duration, Bin) ->
    <<Bin/binary, (enc_time(Duration))/binary>>.

%% @doc 8.3.5.8 / 9.4.2.9 Timestamp p40 / p164: Translate external
%% binary timestamp to internal representation.
dec_ts(Endianness, Bin) ->
    dec_time(Endianness, Bin).

dec_duration(Endianness, Bin) ->
    dec_time(Endianness, Bin).

dec_time(Endianness, Bin) ->
    {Seconds, Rest1} = dec_long(Endianness, Bin),
    {Fraction, Rest2} = dec_unsigned_long(Endianness, Rest1),
    case {Seconds, Fraction} of
	{0, 0} ->
	    {zero, Rest2};
	{-1, 16#ffffffff} ->
	    {invalid, Rest2};
	{16#7fffffff, 16#ffffffff} ->
	    {infinity, Rest2};
	_ ->
	    {#time{seconds = Seconds, fraction = Fraction}, Rest2}
    end.


%% @doc 8.3.5.9 / 9.4.2.11 ParameterList p41 / p164: transform
%% parameter list to binary external presentation.
%% @see dec_parl/2.

enc_parl(Parameter_list) ->
    enc_parl(Parameter_list, <<>>).

enc_parl([], Bin) ->
    <<Bin/binary, (enc_short(?PID_SENTINEL))/binary, (enc_short(0))/binary>>;
%% TODO: How to handle ?PID_PAD,parameters?
enc_parl([#parameter{id = pad, value = N} | Parameters], Bin) ->
    Bin1 = enc_short(?PID_PAD),
    Bin2 = enc_short(N * 4),
    enc_parl(Parameters, <<Bin/binary, Bin1/binary, Bin2/binary, 0:N/unit:32>>);
enc_parl([#parameter{id = Id, value = Value} | Parameters], Bin) ->
    {Id1, Value1} = enc_par(Id, Value),
    {Padded_value, L, P} = pad32(Value1),
    Bin1 = enc_short(Id1),
    Bin2 = enc_short(L + P),
    enc_parl(Parameters, <<Bin/binary, Bin1/binary, Bin2/binary, Padded_value/binary>>).

%% @doc 8.3.5.9 / 9.4.2.11 ParameterList p41 / p164: binary external
%% to internal presentation.
%% @see enc_part/1.
dec_parl(Endianness, Bin) ->
    dec_parl(Endianness, dec_short(Endianness, Bin), []).
%% @private
dec_parl(Endianness, {?PID_SENTINEL, Bin}, Parameters) ->
    {_, Rest} = dec_short(Endianness, Bin),
    {Parameters, Rest};
dec_parl(Endianness, {?PID_PAD, Bin}, Parameters) ->
    {N, Rest1} = dec_short(Endianness, Bin),
    <<_:N/bytes, Rest2/binary>> = Rest1,
    dec_parl(Endianness, dec_short(Endianness, Rest2), Parameters);
dec_parl(Endianness, {Id, Bin}, Parameters) ->
    {N, Rest1} = dec_short(Endianness, Bin),
    <<Value:N/bytes, Rest2/binary>> = Rest1,
    {Id1, Value1} = dec_par(Endianness, Id, Value),
    Parameter = #parameter{id = Id1, value = Value1},
    dec_parl(Endianness, dec_short(Endianness, Rest2), [Parameter | Parameters]).

%% Encode parameter: also see Table 9.12 - ParameterId Values
enc_par(user_data, Value) ->
    {?PID_USER_DATA, Value};
enc_par(topic_name, Value) ->
    {?PID_TOPIC_NAME, enc_string(Value)};
enc_par(type_name, Value) ->
    {?PID_TYPE_NAME, enc_string(Value)};
enc_par(group_data, Value) ->
    {?PID_GROUP_DATA, Value};
enc_par(topic_data, Value) ->
    {?PID_TOPIC_DATA, Value};
enc_par(durability, Value) ->
    {?PID_DURABILITY, Value};
enc_par(durability_service, Value) ->
    {?PID_DURABILITY_SERVICE, Value};
enc_par(deadline, Value) ->
    {?PID_DEADLINE, Value};
enc_par(latency_budget, Value) ->
    {?PID_LATENCY_BUDGET, Value};
enc_par(liveliness, Value) ->
    {?PID_LIVELINESS, Value};
enc_par(reliability, Value) ->
    {?PID_RELIABILITY, Value};
enc_par(lifespan, Value) ->
    {?PID_LIFESPAN, Value};
enc_par(destination_order, Value) ->
    {?PID_DESTINATION_ORDER, Value};
enc_par(history, Value) ->
    {?PID_HISTORY, Value};
enc_par(resource_limits, Value) ->
    {?PID_RESOURCE_LIMITS, Value};
enc_par(ownership, Value) ->
    {?PID_OWNERSHIP, Value};
enc_par(ownership_strength, Value) ->
    {?PID_OWNERSHIP_STRENGTH, Value};
enc_par(presentation, Value) ->
    {?PID_PRESENTATION, Value};
enc_par(partition, Value) ->
    {?PID_PARTITION, Value};
enc_par(time_based_filter, Value) ->
    {?PID_TIME_BASED_FILTER, Value};
enc_par(transport_priority, Value) ->
    {?PID_TRANSPORT_PRIORITY, Value};
enc_par(protocol_version, Value) ->
    {?PID_PROTOCOL_VERSION, enc_prot_ver(Value)};
enc_par(vendor_id, Value) ->
    {?PID_VENDOR_ID, enc_vendor(Value)};
enc_par(unicast_locator, Value) ->
    {?PID_UNICAST_LOCATOR, enc_loc(Value)};
enc_par(multicast_locator, Value) ->
    {?PID_MULTICAST_LOCATOR, enc_loc(Value)};
enc_par(multicast_ipaddress, Value) ->
    {?PID_MULTICAST_IPADDRESS, enc_ipv4address(Value)};
enc_par(default_unicast_locator, Value) ->
    {?PID_DEFAULT_UNICAST_LOCATOR, enc_loc(Value)};
enc_par(default_multicast_locator, Value) ->
    {?PID_DEFAULT_MULTICAST_LOCATOR, enc_loc(Value)};
enc_par(metatraffic_unicast_locator, Value) ->
    {?PID_METATRAFFIC_UNICAST_LOCATOR, enc_loc(Value)};
enc_par(metatraffic_multicast_locator, Value) ->
    {?PID_METATRAFFIC_MULTICAST_LOCATOR, enc_loc(Value)};
enc_par(default_unicast_ipaddress, Value) ->
    {?PID_DEFAULT_UNICAST_IPADDRESS, enc_ipv4address(Value)};
enc_par(default_unicast_port, Value) ->
    {?PID_DEFAULT_UNICAST_PORT, enc_port(Value)};
enc_par(metatraffic_unicast_ipaddress, Value) ->
    {?PID_METATRAFFIC_UNICAST_IPADDRESS, enc_ipv4address(Value)};
enc_par(metatraffic_unicast_port, Value) ->
    {?PID_METATRAFFIC_UNICAST_PORT, enc_port(Value)};
enc_par(metatraffic_multicast_ipaddress, Value) ->
    {?PID_METATRAFFIC_MULTICAST_IPADDRESS, enc_ipv4address(Value)};
enc_par(metatraffic_multicast_port, Value) ->
    {?PID_METATRAFFIC_MULTICAST_PORT, enc_port(Value)};
enc_par(expects_inline_qos, Value) ->
    {?PID_EXPECTS_INLINE_QOS, enc_bool(Value)};
enc_par(participant_manual_liveliness_count, Value) ->
    {?PID_PARTICIPANT_MANUAL_LIVELINESS_COUNT, enc_count(Value)};
enc_par(participant_builtin_endpoints, Value) ->
    {?PID_PARTICIPANT_BUILTIN_ENDPOINTS, enc_unsigned_long(Value)};
enc_par(participant_lease_duration, Value) ->
    {?PID_PARTICIPANT_LEASE_DURATION, enc_duration(Value)};
enc_par(content_filter_property, Value) ->
    {?PID_CONTENT_FILTER_PROPERTY, Value};
enc_par(participant_guid, Value) ->
    {?PID_PARTICIPANT_GUID, enc_guid(Value)};
enc_par(participant_entityid, Value) ->
    {?PID_PARTICIPANT_ENTITYID, enc_entity_id(Value)};
enc_par(group_guid, Value) ->
    {?PID_GROUP_GUID, enc_guid(Value)};
enc_par(group_entityid, Value) ->
    {?PID_GROUP_ENTITYID, enc_entity_id(Value)};
%% enc_par(builtin_endpoint_set, Value) ->
%%     {?PID_BUILTIN_ENDPOINT_SET, enc_unsigned_long(Value)};
enc_par(builtin_endpoint_set, Value) ->
    {?PID_BUILTIN_ENDPOINT_SET, enc_set(Value, ?BuiltinEndpointSet)};
enc_par(property_list, Value) ->
    {?PID_PROPERTY_LIST, Value};
enc_par(type_max_size_serialized, Value) ->
    {?PID_TYPE_MAX_SIZE_SERIALIZED, enc_long(Value)};
enc_par(entity_name, Value) ->
    {?PID_ENTITY_NAME, Value};
enc_par(key_hash, Value) ->
    {?PID_KEY_HASH, Value};
enc_par(status_info, Value) ->
    {?PID_STATUS_INFO, Value};
enc_par(content_filter_info, Value) ->
    {?PID_CONTENT_FILTER_INFO, Value};
enc_par(coherent_set, Value) ->
    {?PID_COHERENT_SET, Value};
enc_par(directed_write, Value) ->
    {?PID_DIRECTED_WRITE, Value};
enc_par(original_writer_info, Value) ->
    {?PID_ORIGINAL_WRITER_INFO, Value};
enc_par(Id, Value) when Id > ?PID_SENTINEL ->
    {Id, Value}.

-define(V(Tuple), element(1, Tuple)).

%% Decode parameter: also see Table 9.12 - ParameterId Values
dec_par(_Endianness, ?PID_USER_DATA, Value) ->
    {user_data, Value};
dec_par(Endianness, ?PID_TOPIC_NAME, Value) ->
    {topic_name, ?V(dec_string(Endianness, Value))};
dec_par(Endianness, ?PID_TYPE_NAME, Value) ->
    {type_name, ?V(dec_string(Endianness, Value))};
dec_par(_Endianness, ?PID_GROUP_DATA, Value) ->
    {group_data, Value};
dec_par(_Endianness, ?PID_TOPIC_DATA, Value) ->
    {topic_data, Value};
dec_par(_Endianness, ?PID_DURABILITY, Value) ->
    {durability, Value};
dec_par(_Endianness, ?PID_DURABILITY_SERVICE, Value) ->
    {durability_service, Value};
dec_par(_Endianness, ?PID_DEADLINE, Value) ->
    {deadline, Value};
dec_par(_Endianness, ?PID_LATENCY_BUDGET, Value) ->
    {latency_budget, Value};
dec_par(_Endianness, ?PID_LIVELINESS, Value) ->
    {liveliness, Value};
dec_par(_Endianness, ?PID_RELIABILITY, Value) ->
    {reliability, Value};
dec_par(_Endianness, ?PID_LIFESPAN, Value) ->
    {lifespan, Value};
dec_par(_Endianness, ?PID_DESTINATION_ORDER, Value) ->
    {destination_order, Value};
dec_par(_Endianness, ?PID_HISTORY, Value) ->
    {history, Value};
dec_par(_Endianness, ?PID_RESOURCE_LIMITS, Value) ->
    {resource_limits, Value};
dec_par(_Endianness, ?PID_OWNERSHIP, Value) ->
    {ownership, Value};
dec_par(_Endianness, ?PID_OWNERSHIP_STRENGTH, Value) ->
    {ownership_strength, Value};
dec_par(_Endianness, ?PID_PRESENTATION, Value) ->
    {presentation, Value};
dec_par(_Endianness, ?PID_PARTITION, Value) ->
    {partition, Value};
dec_par(_Endianness, ?PID_TIME_BASED_FILTER, Value) ->
    {time_based_filter, Value};
dec_par(_Endianness, ?PID_TRANSPORT_PRIORITY, Value) ->
    {transport_priority, Value};
dec_par(_Endianness, ?PID_PROTOCOL_VERSION, Value) ->
    {protocol_version, ?V(dec_prot_ver(Value))};
dec_par(_Endianness, ?PID_VENDOR_ID, Value) ->
    {vendor_id, ?V(dec_vendor(Value))};
dec_par(Endianness, ?PID_UNICAST_LOCATOR, Value) ->
    {unicast_locator, ?V(dec_loc(Endianness, Value))};
dec_par(Endianness, ?PID_MULTICAST_LOCATOR, Value) ->
    {multicast_locator, ?V(dec_loc(Endianness, Value))};
dec_par(Endianness, ?PID_MULTICAST_IPADDRESS, Value) ->
    {multicast_ipaddress, ?V(dec_ipv4address(Endianness,Value))};
dec_par(Endianness, ?PID_DEFAULT_UNICAST_LOCATOR, Value) ->
    {default_unicast_locator, ?V(dec_loc(Endianness, Value))};
dec_par(Endianness, ?PID_DEFAULT_MULTICAST_LOCATOR, Value) ->
    {default_multicast_locator, ?V(dec_loc(Endianness, Value))};
dec_par(Endianness, ?PID_METATRAFFIC_UNICAST_LOCATOR, Value) ->
    {metatraffic_unicast_locator, ?V(dec_loc(Endianness, Value))};
dec_par(Endianness, ?PID_METATRAFFIC_MULTICAST_LOCATOR, Value) ->
    {metatraffic_multicast_locator, ?V(dec_loc(Endianness, Value))};
dec_par(Endianness, ?PID_DEFAULT_UNICAST_IPADDRESS, Value) ->
    {default_unicast_ipaddress, ?V(dec_ipv4address(Endianness,Value))};
dec_par(Endianness, ?PID_DEFAULT_UNICAST_PORT, Value) ->
    {default_unicast_port, ?V(dec_port(Endianness, Value))};
dec_par(Endianness, ?PID_METATRAFFIC_UNICAST_IPADDRESS, Value) ->
    {metatraffic_unicast_ipaddress, ?V(dec_ipv4address(Endianness,Value))};
dec_par(Endianness, ?PID_METATRAFFIC_UNICAST_PORT, Value) ->
    {metatraffic_unicast_port, ?V(dec_port(Endianness, Value))};
dec_par(Endianness, ?PID_METATRAFFIC_MULTICAST_IPADDRESS, Value) ->
    {metatraffic_multicast_ipaddress, ?V(dec_ipv4address(Endianness,Value))};
dec_par(Endianness, ?PID_METATRAFFIC_MULTICAST_PORT, Value) ->
    {metatraffic_multicast_port, ?V(dec_port(Endianness, Value))};
dec_par(_Endianness, ?PID_EXPECTS_INLINE_QOS, Value) ->
    {expects_inline_qos, ?V(dec_bool(Value))};
dec_par(Endianness, ?PID_PARTICIPANT_MANUAL_LIVELINESS_COUNT, Value) ->
    {participant_manual_liveliness_count, ?V(dec_count(Endianness, Value))};
dec_par(Endianness, ?PID_PARTICIPANT_BUILTIN_ENDPOINTS, Value) ->
    {participant_builtin_endpoints, ?V(dec_unsigned_long(Endianness, Value))};
dec_par(Endianness, ?PID_PARTICIPANT_LEASE_DURATION, Value) ->
    {participant_lease_duration, ?V(dec_duration(Endianness, Value))};
dec_par(_Endianness, ?PID_CONTENT_FILTER_PROPERTY, Value) ->
    {content_filter_property, Value};
dec_par(_Endianness, ?PID_PARTICIPANT_GUID, Value) ->
    %% dec_guid is a little different from the other dec_'s
    {participant_guid, dec_guid(Value)};
dec_par(_Endianness, ?PID_PARTICIPANT_ENTITYID, Value) ->
    {participant_entityid, ?V(dec_entity_id(Value))};
dec_par(_Endianness, ?PID_GROUP_GUID, Value) ->
    {group_guid, dec_guid(Value)};
dec_par(_Endianness, ?PID_GROUP_ENTITYID, Value) ->
    {group_entityid, ?V(dec_entity_id(Value))};
%% dec_par(_Endianness, ?PID_BUILTIN_ENDPOINT_SET, Value) ->
%%     {builtin_endpoint_set, Value};
dec_par(Endianness, ?PID_BUILTIN_ENDPOINT_SET, Value) ->
    {builtin_endpoint_set, ?V(dec_set(Endianness, Value, ?BuiltinEndpointSet))};
dec_par(_Endianness, ?PID_PROPERTY_LIST, Value) ->
    {property_list, Value};
dec_par(Endianness, ?PID_TYPE_MAX_SIZE_SERIALIZED, Value) ->
    {type_max_size_serialized, ?V(dec_long(Endianness, Value))};
dec_par(_Endianness, ?PID_ENTITY_NAME, Value) ->
    {entity_name, Value};
dec_par(_Endianness, ?PID_KEY_HASH, Value) ->
    {key_hash, Value};
dec_par(_Endianness, ?PID_STATUS_INFO, Value) ->
    {status_info, Value};
dec_par(_Endianness, ?PID_CONTENT_FILTER_INFO, Value) ->
    {content_filter_info, Value};
dec_par(_Endianness, ?PID_COHERENT_SET, Value) ->
    {coherent_set, Value};
dec_par(_Endianness, ?PID_DIRECTED_WRITE, Value) ->
    {directed_write, Value};
dec_par(_Endianness, ?PID_ORIGINAL_WRITER_INFO, Value) ->
    {original_writer_info, Value};
dec_par(_Endianness, Id, Value) when Id > ?PID_SENTINEL ->
    {Id, Value}.

%% @doc 8.3.5.10 / 9.4.2.13 Count
enc_count(Count) ->
    enc_long(Count).

enc_count(Count, Bin) ->
    <<Bin/binary, (enc_count(Count))/binary>>.

%% @doc 8.3.5.10 / 9.4.2.13 Count
dec_count(Endianness, Bin) ->
    dec_long(Endianness, Bin).

%% @doc 8.3.5.11 / 9.4.2.10 LocatorList p42 / p164: translate internal
%% locator list to external binary representation. Empty lists can not
%% be encoded. @see dec_locl/1.
enc_locl(Locator_list) ->
    Bin1 = enc_unsigned_long(length(Locator_list)),
    Bin2 = << <<(enc_loc(Locator))/binary>> || Locator <- Locator_list>>,
    <<Bin1/binary, Bin2/binary>>. 

enc_locl(Locator_list, Bin) ->
    <<Bin/binary, (enc_locl(Locator_list))/binary>>.

%% @doc 8.3.5.11 / 9.4.2.10 LocatorList p42 / p164: translate internal
enc_loc(invalid) ->
    enc_loc(invalid, invalid, invalid);
enc_loc(#locator{kind = Kind, port = Port, address = Address}) ->
    enc_loc(Kind, Port, Address).

enc_loc(Kind, Port, Address) ->
    Kind_b = case Kind of
		 invalid -> ?LOCATOR_KIND_INVALID;
		 reserved -> ?LOCATOR_KIND_RESERVED;
		 udpv4 -> ?LOCATOR_KIND_UDPv4;
		 udpv6 -> ?LOCATOR_KIND_UDPv6;
		 _ -> Kind
	     end,
    Address_b = enc_ipaddress(Address, Kind),
    Bin1 = enc_long(Kind_b),
    Port_b = enc_port(Port),
    <<Bin1/binary, Port_b/binary, Address_b/binary>>.

enc_loc(Locator, Bin) ->
    <<Bin/binary, (enc_loc(Locator))/binary>>.

enc_ipaddress(invalid, _Kind) ->
    <<0:16/big-unit:8>>;
enc_ipaddress({B1, B2, B3, B4}, udpv4) ->
    <<0:12/big-unit:8, B1, B2, B3, B4>>;
enc_ipaddress({W1, W2, W3, W4, W5, W6, W7, W8}, udpv6) ->
    <<W1:16, W2:16, W3:16, W4:16, W5:16, W6:16, W7:16, W8:16>>;
enc_ipaddress(Address, _Kind) ->
    Address.

enc_port(invalid) ->    
    enc_port(?LOCATOR_PORT_INVALID);
enc_port(Port) ->    
    enc_unsigned_long(Port).

%% @doc 8.3.5.11 / 9.4.2.10 LocatorList p42 / p164: translate external
%% binary representation to internal locator list.
%% @see enc_locl/1
dec_locl(Endianness, Bin) ->
    {N, Rest} = dec_unsigned_long(Endianness, Bin),
    dec_locl(Endianness, Rest, N, []).
%% @private

dec_locl(_Endianness, Rest, 0, Locs) ->
    %% TODO: Use lists:reverse() on locs?
    {Locs, Rest};
dec_locl(Endianness, Bin, N, Locs) ->
    {Loc, Rest} = dec_loc(Endianness, Bin),
    dec_locl(Endianness, Rest, N - 1, [Loc | Locs]).

dec_loc(Endianness, Bin) ->
    {Kind_b, Rest1} = dec_kind(Endianness, Bin), 
    Kind = case Kind_b of
	       ?LOCATOR_KIND_INVALID -> invalid;
	       ?LOCATOR_KIND_RESERVED -> reserved;
	       ?LOCATOR_KIND_UDPv4 -> udpv4;
	       ?LOCATOR_KIND_UDPv6 -> udpv6;
	       _ -> Kind_b
	   end,
    {Port, Rest2} = dec_port(Endianness, Rest1),
    <<Address_b:16/bytes, Rest3/binary>> = Rest2,
    Kind = case Kind_b of
	       ?LOCATOR_KIND_INVALID -> invalid;
	       ?LOCATOR_KIND_RESERVED -> reserved;
	       ?LOCATOR_KIND_UDPv4 -> udpv4;
	       ?LOCATOR_KIND_UDPv6 -> udpv6;
	       _ -> Kind_b
	   end,
    Address = dec_ipaddress(Address_b, Kind),
    case {Kind, Port, Address} of
	{invalid, invalid, invalid} -> {invalid, Rest3};
	_ -> {#locator{kind = Kind, port = Port, address = Address}, Rest3}
    end.

dec_ipaddress(<<0:128>>, _Kind) ->
    invalid;
dec_ipaddress(<<0:96, B1, B2, B3, B4>>, udpv4) ->
    {B1, B2, B3, B4};
dec_ipaddress(<<W1:16, W2:16, W3:16, W4:16, W5:16, W6:16, W7:16, W8:16>>, udpv6) ->
    {W1, W2, W3, W4, W5, W6, W7, W8};
dec_ipaddress(Bin, _Kind) ->
    Bin.

dec_port(Endianness, Bin) ->
    case dec_unsigned_long(Endianness, Bin) of
	{?LOCATOR_PORT_INVALID, Rest} ->
	    {invalid, Rest};
	{Port, Rest} ->
	    {Port, Rest}
    end.

dec_kind(Endianness, Bin) ->
    case dec_long(Endianness, Bin) of
	{?LOCATOR_KIND_INVALID, Rest} -> {invalid, Rest};
	{?LOCATOR_KIND_RESERVED, Rest} -> {reserved, Rest};
	{?LOCATOR_KIND_UDPv4, Rest} -> {udpv4, Rest};
	{?LOCATOR_KIND_UDPv6, Rest} -> {udpv6, Rest};
	{Kind, Rest}->
	    {Kind, Rest}
    end.

enc_locudpv4(invalid) ->
    <<?LOCATORUDPv4_INVALID:8/big-unit:8>>;
enc_locudpv4(#locator_udpv4{port = Port, address = Address}) ->
    <<(enc_ipv4address(Address))/binary, (enc_unsigned_long(Port))/binary>>.

enc_ipv4address({B1, B2, B3, B4}) ->
    <<B1, B2, B3, B4>>.

enc_locudpv4(Locator_udp4, Bin) ->
    <<Bin/binary, (enc_locudpv4(Locator_udp4))/binary>>.

%% dec_locudpv4(_Endianness, <<?LOCATORUDPv4_INVALID:8/bytes>>) ->
dec_locudpv4(_Endianness, <<?LOCATORUDPv4_INVALID:64>>) ->
    invalid;
dec_locudpv4(Endianness, Bin) ->
    {Address, Rest1} = dec_ipv4address(Endianness, Bin),
    {Port, Rest2} = dec_port(Endianness, Rest1),
    {#locator_udpv4{port = Port, address = Address}, Rest2}.

dec_ipv4address(Endianness, Bin) ->
    {Bin1, Rest} = dec_unsigned_long(Endianness, Bin),
    Bin2 = Bin1 bsr 8,
    Bin3 = Bin2 bsr 8,
    Bin4 = Bin3 bsr 8,
    Address = {Bin4 band 16#ff, Bin3 band 16#ff, Bin2 band 16#ff, Bin1 band 16#ff},
    {Address, Rest}.

%% Encode a set. For example, the set of BuiltinEndpointSet_t contains
%% some atoms indicating the builtin endpoint types. These symbolic
%% names are to be translated into their OR-ed numerical
%% equivalants. The first argument is the list with the symbolic names
%% and the second argument is the set of all posible combinations.
enc_set(List, Set) ->
    enc_set(List, Set, 0).

enc_set([], _Set, N) ->
    enc_unsigned_long(N);
enc_set([Name | Rest], Set, N) ->
    {_K, V} = lists:keyfind(Name, 1, Set),
    enc_set(Rest, Set, N + V).

dec_set(Endianness, Bin, Set) ->
    {N, Rest} = dec_unsigned_long(Endianness, Bin),
    Fun = fun({K, V}) when V band N =/= 0 ->
		  {true, K};
	     (_) ->
		  false
	  end,
    {lists:filtermap(Fun, Set), Rest}.

%%%=============================================================================
%%% CDR encode / decode functions
%%%=============================================================================

%%-----------------------------------------------------------------
%% short
%%-----------------------------------------------------------------
%%-----------------------------------------------------------------
%% Func: enc_short/2
%%-----------------------------------------------------------------
enc_short(X)
  when is_integer(X) andalso X >= ?SHORTMIN andalso X =< ?SHORTMAX -> 
    <<X:16/big-signed-integer>>.

enc_short(X, Bin)
  when is_integer(X) andalso X >= ?SHORTMIN andalso X =< ?SHORTMAX -> 
    <<Bin/binary, (enc_short(X))/binary>>.

%%-----------------------------------------------------------------
%% Func: dec_short/2
%%-----------------------------------------------------------------
dec_short(big, <<Short:16/big-signed-integer, Rest/binary>>) ->
    {Short, Rest};
dec_short(little, <<Short:16/little-signed-integer, Rest/binary>>) ->
    {Short, Rest}.

%%-----------------------------------------------------------------
%% unsigned short
%%-----------------------------------------------------------------
%%-----------------------------------------------------------------
%% Func: enc_unsigned_short/2
%%-----------------------------------------------------------------
enc_unsigned_short(X)
  when is_integer(X), X >= ?USHORTMIN, X =< ?USHORTMAX -> 
    <<X:16/big-unsigned-integer>>.

enc_unsigned_short(X, Bin) ->
    <<Bin/binary, (enc_unsigned_short(X))/binary>>.

%%-----------------------------------------------------------------
%% Func: dec_unsigned_short/2
%%-----------------------------------------------------------------
dec_unsigned_short(big, <<UShort:16/big-unsigned-integer, Rest/binary>>) ->
    {UShort, Rest};
dec_unsigned_short(little, <<UShort:16/little-unsigned-integer, Rest/binary>>) ->
    {UShort, Rest}.

%%-----------------------------------------------------------------
%% long
%%-----------------------------------------------------------------
%%-----------------------------------------------------------------
%% Func: enc_long/2
%%-----------------------------------------------------------------
enc_long(X)
  when is_integer(X), X >= ?LONGMIN, X =< ?LONGMAX -> 
    <<X:32/big-signed-integer>>.

enc_long(X, Bin) ->
    <<Bin/binary, (enc_long(X))/binary>>.

%%-----------------------------------------------------------------
%% Func: dec_long/2
%%-----------------------------------------------------------------
dec_long(big, <<Long:32/big-signed-integer, Rest/binary>>) ->
    {Long, Rest};
dec_long(little, <<Long:32/little-signed-integer, Rest/binary>>) ->
    {Long, Rest}.

%%-----------------------------------------------------------------
%% unsigned_long
%%-----------------------------------------------------------------
%%-----------------------------------------------------------------
%% Func: enc_unsigned_long/2
%%-----------------------------------------------------------------
enc_unsigned_long(X)
  when is_integer(X), X >= ?ULONGMIN, X =< ?ULONGMAX ->
    <<X:32/big-unsigned-integer>>.

enc_unsigned_long(X, Bin) ->
    <<Bin/binary, (enc_unsigned_long(X))/binary>>.

%%-----------------------------------------------------------------
%% Func: dec_unsigned_long/2
%%-----------------------------------------------------------------
dec_unsigned_long(big, <<ULong:32/big-unsigned-integer, Rest/binary>>) ->
    {ULong, Rest};
dec_unsigned_long(little, <<ULong:32/little-unsigned-integer, Rest/binary>>) ->
    {ULong, Rest}.

%%-----------------------------------------------------------------
%% boolean
%%-----------------------------------------------------------------
%%-----------------------------------------------------------------
%% Func: enc_bool/2
%%-----------------------------------------------------------------
enc_bool(true) -> <<1:8>>;
enc_bool(false) -> <<0:8>>.

enc_bool(X, Bin) -> <<Bin/binary, (enc_bool(X))/binary>>.

%%-----------------------------------------------------------------
%% Func: dec_bool/1
%%-----------------------------------------------------------------
dec_bool(<<1:8, Rest/binary>>) -> {true, Rest};
dec_bool(<<0:8, Rest/binary>>) -> {false, Rest}.

%%-----------------------------------------------------------------
%% Func: enc_float/2
%%-----------------------------------------------------------------
enc_float(X)
  when is_number(X) ->
    <<X:32/big-float>>.

enc_float(X, Bin) ->
    <<Bin/binary, (enc_float(X))/binary>>.

%%-----------------------------------------------------------------
%% Func: dec_float/2
%%-----------------------------------------------------------------
dec_float(big, <<Float:32/big-float, Rest/binary>>) ->
    {Float, Rest};
dec_float(little, <<Float:32/little-float, Rest/binary>>) ->
    {Float, Rest}.

%%-----------------------------------------------------------------
%% Func: enc_double/2
%%-----------------------------------------------------------------
enc_double(X)
  when is_number(X) ->
    <<X:64/big-float>>.

enc_double(X, Bin) ->
    <<Bin/binary, (enc_double(X))/binary>>.

%%-----------------------------------------------------------------
%% Func: dec_double/2
%%-----------------------------------------------------------------
dec_double(big, <<Double:64/big-float, Rest/binary>>) ->
    {Double, Rest};
dec_double(little, <<Double:64/little-float, Rest/binary>>) ->
    {Double, Rest}.

%%-----------------------------------------------------------------
%% char
%%-----------------------------------------------------------------
%%-----------------------------------------------------------------
%% Func: enc_char/2
%%-----------------------------------------------------------------
enc_char(X)
  when is_integer(X), 0 =< X, X =< 255 -> 
    <<X:8>>.

enc_char(X, Bin) ->
    <<Bin/binary, (enc_char(X))/binary>>.

%%-----------------------------------------------------------------
%% Func: dec_char/1
%%-----------------------------------------------------------------
dec_char(<<Char:8, Rest/binary>>) ->
    {Char, Rest}.

%%-----------------------------------------------------------------
%% octet
%%-----------------------------------------------------------------
%%-----------------------------------------------------------------
%% Func: enc_octet/2
%%-----------------------------------------------------------------
enc_octet(X)
  when is_integer(X), 0 =< X, X =< 255 -> 
    <<X:8>>.

enc_octet(X, Bin) ->
    <<Bin/binary, (enc_octet(X))/binary>>.
   
%%-----------------------------------------------------------------
%% Func: dec_octet/1
%%-----------------------------------------------------------------
dec_octet(<<Octet:8/big-unsigned-integer, Rest/binary>>) ->
    {Octet, Rest}.

%%-----------------------------------------------------------------
%% enum
%%-----------------------------------------------------------------
%%-----------------------------------------------------------------
%% Func: enc_enum/3
%%-----------------------------------------------------------------
enc_enum(X, Enums) ->
    Val = get_enum_value(X, Enums, 0),
    enc_unsigned_long(Val).

enc_enum(X, Enums, Bin) ->
    <<Bin/binary, (enc_enum(X, Enums))/binary>>.

get_enum_value(X, [X | _Enums], N) ->
    N;
get_enum_value(X, [_X  | Enums], N) ->
    get_enum_value(X, Enums, N + 1).

%%-----------------------------------------------------------------
%% Func: dec_enum/2
%% TODO: uses list to atom with potential problem with size of atom
%% table. Is this the proper way to go?
%%-----------------------------------------------------------------
dec_enum(Endianness, Enums, Bin) ->
    {N, Rest}  = dec_unsigned_long(Endianness, Bin),
    {lists:nth(N + 1, Enums), Rest}.


%%-----------------------------------------------------------------
%% IIOP 1.1 - 
%%-----------------------------------------------------------------

%%-----------------------------------------------------------------
%% longlong 
%%-----------------------------------------------------------------
%%-----------------------------------------------------------------
%% Func: enc_longlong/2
%%-----------------------------------------------------------------
enc_longlong(X)
  when is_integer(X), X >= ?LONGLONGMIN, X =< ?LONGLONGMAX -> 
    <<X:64/big-signed-integer>>.

enc_longlong(X, Bin) ->
    <<Bin/binary, (enc_longlong(X))/binary>>.

%%-----------------------------------------------------------------
%% Func: dec_longlong/2
%%-----------------------------------------------------------------
dec_longlong(big, <<LongLong:64/big-signed-integer, Rest/binary>>) ->
    {LongLong, Rest};
dec_longlong(little, <<LongLong:64/little-signed-integer, Rest/binary>>) ->
    {LongLong, Rest}.

%%-----------------------------------------------------------------
%% Func: enc_unsigned_longlong/2
%%-----------------------------------------------------------------
enc_unsigned_longlong(X)
  when is_integer(X) andalso X >= ?ULONGLONGMIN andalso X =< ?ULONGLONGMAX ->
    <<X:64/big-unsigned-integer>>.

enc_unsigned_longlong(X, Bin) ->
    <<Bin/binary, (enc_unsigned_longlong(X))/binary>>.

%%-----------------------------------------------------------------
%% Func: dec_unsigned_longlong/2
%%-----------------------------------------------------------------
dec_unsigned_longlong(big, <<ULongLong:64/big-unsigned-integer, Rest/binary>>) ->
    {ULongLong, Rest};
dec_unsigned_longlong(little, <<ULongLong:64/little-unsigned-integer, Rest/binary>>) ->
    {ULongLong, Rest}.

%%-----------------------------------------------------------------
%% Func: enc_sequence/2
%%-----------------------------------------------------------------
%% enc_sequence(Sequence, Fun) ->
%%     enc_sequence(Sequence, Fun, <<>>).

enc_sequence(Sequence)
  when is_binary(Sequence) ->
    Bin1 = enc_unsigned_long(byte_size(Sequence)),
    <<Bin1/binary, Sequence/binary>>.

enc_sequence(Sequence, Bin)
  when is_binary(Bin) ->
    <<Bin/binary, (enc_sequence(Sequence))/binary>>;
enc_sequence(Sequence, Fun)
  when is_list(Sequence), is_function(Fun)  ->
    Bin1 = enc_unsigned_long(length(Sequence)),
    lists:foldl(Fun, Bin1, Sequence).

enc_sequence(Sequence, Fun, Bin) ->
    <<Bin/binary, (enc_sequence(Sequence, Fun))/binary>>.
    
dec_sequence(Endianness, Bin) ->
    {N, Rest1} = dec_unsigned_long(Endianness, Bin),
    <<Sequence:N/bytes, Rest2/binary>> = Rest1,
    {Sequence, Rest2}.

dec_sequence(Endianness, Fun, Bin) ->
    {N, Rest} = dec_unsigned_long(Endianness, Bin),
    dec_sequence(Endianness, N, Rest, Fun, []).

%%-----------------------------------------------------------------
%% Func: dec_sequence/3
%%-----------------------------------------------------------------
dec_sequence(_Endianness, 0, Bin, _Fun, Sequence) ->
    {lists:reverse(Sequence), Bin};
dec_sequence(Endianness, N, Bin, Fun, Sequence) ->
    {Item, Rest} = Fun(Endianness, Bin),
    dec_sequence(Endianness, N - 1, Rest, Fun, [Item | Sequence]).

enc_string(X)
  when is_list(X) ->
    enc_string(list_to_binary(X));
enc_string(X)
  when is_binary(X) -> 
    Bin = enc_unsigned_long(byte_size(X) + 1),
    <<Bin/binary, X/binary, 0>>.

enc_string(X, Bin) ->
    <<Bin/binary, (enc_string(X))/binary>>.

dec_string(Endianness, Bin) ->
    {N, Rest} = dec_unsigned_long(Endianness, Bin),
    L = N - 1,
    <<String:L/big-binary, 0, Rest1/binary>> = Rest,
    {binary_to_list(String), Rest1}.

%%%=============================================================================
%%% Encode / decode CDR encapsulated data
%%% We currently always encode using big-endian
%%%=============================================================================

enc_pmd(#participant_message_data{participant_guid_prefix = Guid_prefix, kind = Kind, data = Data}) ->
    Bin1 = enc_guid_prefix(Guid_prefix),
    Bin2 = case Kind of
	       unknown -> 
		   ?PARTICIPANT_MESSAGE_DATA_KIND_UNKNOWN;
	       automatic_liveliness_update ->
		   ?PARTICIPANT_MESSAGE_DATA_KIND_AUTOMATIC_LIVELINESS_UPDATE;
	       manual_liveliness_update ->
		   ?PARTICIPANT_MESSAGE_DATA_KIND_MANUAL_LIVELINESS_UPDATE
	   end,
    Bin3 = enc_data(Data),
    <<Bin1/binary, Bin2/binary, Bin3/binary>>.

%% Klopt niet!
dec_pmd(<<Bin1:96/binary, Bin2:32/binary, Data/binary>>) ->
    {Guid_prefix, _} = dec_guid_prefix(Bin1),
    Kind = case Bin2 of
	       ?PARTICIPANT_MESSAGE_DATA_KIND_UNKNOWN ->
		   unknown;
	       ?PARTICIPANT_MESSAGE_DATA_KIND_AUTOMATIC_LIVELINESS_UPDATE ->
		   automatic_liveliness_update;
	       ?PARTICIPANT_MESSAGE_DATA_KIND_MANUAL_LIVELINESS_UPDATE ->
		   manual_liveliness_update
	   end,
    #participant_message_data{participant_guid_prefix = Guid_prefix, kind = Kind, data = Data}.

-define(ENC_OPTIONS, 0).

enc_data(Parameter_list)
  when is_list(Parameter_list) ->
    Value = enc_parl(Parameter_list),
    <<?PL_CDR_BE:16, ?ENC_OPTIONS:16, Value/binary>>;
enc_data(Value)
  when is_binary(Value) ->
    Length = enc_unsigned_long(byte_size(Value)),
    <<?CDR_BE:16, ?ENC_OPTIONS:16, Length/binary, Value/binary>>.

dec_data(<<?CDR_BE:16, _:16, Bin/binary>>) ->
    {big, Bin};
dec_data(<<?CDR_LE:16, _:16, Bin/binary>>) ->
    {litte, Bin};
dec_data(<<?PL_CDR_BE:16, _:16, Bin/binary>>) ->
    dec_parl(big, Bin);
dec_data(<<?PL_CDR_LE:16, _:16, Bin/binary>>) ->
    dec_parl(little, Bin).

%% @doc Pad a binary to the next 32 bits boundary and return a tuple
%% with the padded binary, the length of the original binary and the
%% number of bytes added.
pad32(Bin) ->
    Length = byte_size(Bin),
    %% TODO: Optimize using '(4 - (Length band 3)) band 3' or '(-Length) band 3'
    Padding = (4 - (Length rem 4)) rem 4,
    {<<Bin/binary, 0:Padding/unit:8>>, Length, Padding}.
    
%%%===================================================================
%%% Tests
%%%===================================================================

-ifdef(EUNIT).

%% TODO: Hopelessly OUTDATED 
%% TODO: add some more serious tests and use property based testing.

to_sequence_number_set_test_() ->
   [?_assertEqual({#sequence_number_set{base = 1, set = [], num_bits = 1}, []},
		  to_sequence_number_set(1, [])),
    ?_assertEqual({#sequence_number_set{base = 1, set = [1], num_bits = 1}, []},
		  to_sequence_number_set(1, [1])),
    ?_assertEqual({#sequence_number_set{base = 1, set = [1, 2], num_bits = 2}, []},
		  to_sequence_number_set(1, [1, 2])),
    ?_assertEqual({#sequence_number_set{base = 1, set = [3,5,7], num_bits = 7}, []},
		  to_sequence_number_set(1, [3,5,7])),
    ?_assertEqual({#sequence_number_set{base = 3, set = [3,5,7], num_bits = 5}, []},
		  to_sequence_number_set(3, [3,5,7])),
    ?_assertEqual({#sequence_number_set{base = 1, set = lists:seq(2, 100), num_bits = 100}, []},
		  to_sequence_number_set(1, lists:seq(2, 100))),
    ?_assertEqual({#sequence_number_set{base = 1, set = lists:seq(3, 100), num_bits = 100}, []},
		  to_sequence_number_set(1, lists:seq(3, 100))),
    ?_assertEqual({#sequence_number_set{base = 1, set = lists:seq(1, 256), num_bits = 256}, []},
		  to_sequence_number_set(1, lists:seq(1, 256))),
    ?_assertEqual({#sequence_number_set{base = 1, set = lists:seq(1, 256), num_bits = 256}, [257]},
		  to_sequence_number_set(1, lists:seq(1, 257))),
    ?_assertEqual({#sequence_number_set{base = 1, set = lists:seq(1, 5), num_bits = 5}, [257]},
		  to_sequence_number_set(1, lists:seq(1, 5) ++ [257])),
    ?_assertEqual({#sequence_number_set{base = 1, set = [], num_bits = 1}, [257]},
		  to_sequence_number_set(1, [257]))
   ].

to_fragment_number_set_test_() ->
    [?_assertEqual({#fragment_number_set{base = 1, set = [], num_bits = 1}, []},
		  to_fragment_number_set(1, [])),
    ?_assertEqual({#fragment_number_set{base = 1, set = [1], num_bits = 1}, []},
		  to_fragment_number_set(1, [1])),
    ?_assertEqual({#fragment_number_set{base = 1, set = [1, 2], num_bits = 2}, []},
		  to_fragment_number_set(1, [1, 2])),
    ?_assertEqual({#fragment_number_set{base = 3, set = [3], num_bits = 1}, []},
		  to_fragment_number_set(3, [3])),
    ?_assertEqual({#fragment_number_set{base = 3, set = [3, 4], num_bits = 2}, []},
		  to_fragment_number_set(3, [3, 4])),
    ?_assertEqual({#fragment_number_set{base = 1, set = [3,5,7], num_bits = 8}, []},
		  to_fragment_number_set(1, [3,5,7])),
    ?_assertEqual({#fragment_number_set{base = 3, set = [3,5,7], num_bits = 5}, []},
		  to_fragment_number_set(3, [3,5,7])),
    ?_assertEqual({#fragment_number_set{base = 1, set = lists:seq(1, 100), num_bits = 100}, []},
		  to_fragment_number_set(1, lists:seq(1, 100))),
    ?_assertEqual({#fragment_number_set{base = 1, set = lists:seq(3, 100), num_bits = 100}, []},
		  to_fragment_number_set(1, lists:seq(3, 100))),
    ?_assertEqual({#fragment_number_set{base = 3, set = lists:seq(3, 100), num_bits = 98}, []},
		  to_fragment_number_set(3, lists:seq(3, 100))),
    ?_assertEqual({#fragment_number_set{base = 1, set = lists:seq(1, 256), num_bits = 256}, []},
		  to_fragment_number_set(1, lists:seq(1, 256))),
    ?_assertEqual({#fragment_number_set{base = 1, set = lists:seq(1, 256), num_bits = 256}, [257]},
		  to_fragment_number_set(1, lists:seq(1, 257))),
    ?_assertEqual({#fragment_number_set{base = 1, set = lists:seq(1, 4), num_bits = 4}, [257]},
		  to_fragment_number_set(1, lists:seq(1, 4) ++ [257])),
    ?_assertEqual({#fragment_number_set{base = 1, set = [], num_bits = 1}, [257]},
		  to_fragment_number_set(1, [257]))
   ].

enc_guid_prefix_test_() ->
    [?_assertEqual(<<0,1,2,3,4,5,6,7,8,9,10,11>>,
		   enc_guid_prefix(<<0,1,2,3,4,5,6,7,8,9,10,11>>, <<>>)),
     ?_assertEqual(<<0,0,0,0,0,0,0,0,0,0,0,0>>,
		   enc_guid_prefix(unknown, <<>>))].

dec_guid_prefix_test_() ->
    [?_assertEqual({<<0,1,2,3,4,5,6,7,8,9,10,11>>, <<"abc">>},
		   dec_guid_prefix(<<0,1,2,3,4,5,6,7,8,9,10,11,"abc">>)),
     ?_assertEqual({unknown, <<"abc">>},
		   dec_guid_prefix(<<0,0,0,0,0,0,0,0,0,0,0,0,"abc">>))].

enc_entity_id_test_() ->
    [?_assertEqual(<<1,2,3,2>>, enc_entity_id(#entity_id{type=user, kind=writer_with_key, key= <<1,2,3>>}, <<>>)),
     ?_assertEqual(<<0,0,1,16#c1>>, enc_entity_id(participant, <<>>)),
     ?_assertEqual(<<0,0,2,16#c2>>, enc_entity_id('SEDPbuiltinTopicWriter', <<>>)),
     ?_assertEqual(<<0,0,2,16#c7>>, enc_entity_id('SEDPbuiltinTopicReader', <<>>)),
     ?_assertEqual(<<0,0,3,16#c2>>, enc_entity_id('SEDPbuiltinPublicationsWriter', <<>>)),
     ?_assertEqual(<<0,0,3,16#c7>>, enc_entity_id('SEDPbuiltinPublicationsReader', <<>>)),
     ?_assertEqual(<<0,0,4,16#c2>>, enc_entity_id('SEDPbuiltinSubscriptionsWriter', <<>>)),
     ?_assertEqual(<<0,0,4,16#c7>>, enc_entity_id('SEDPbuiltinSubscriptionsReader', <<>>)),
     ?_assertEqual(<<0,1,0,16#c2>>, enc_entity_id('SPDPbuiltinParticipantWriter', <<>>)),
     ?_assertEqual(<<0,1,0,16#c7>>, enc_entity_id('SPDPbuiltinParticipantReader', <<>>)),
     ?_assertEqual(<<0,2,0,16#c2>>, enc_entity_id('BuiltinParticipantMessageWriter', <<>>)),
     ?_assertEqual(<<0,2,0,16#c7>>, enc_entity_id('BuiltinParticipantMessageReader', <<>>)),
     ?_assertEqual(<<0,0,0,0>>, enc_entity_id(unknown, <<>>))].

dec_entity_id_test_() ->
    [?_assertEqual({#entity_id{type=user, kind=writer_with_key, key= <<1,2,3>>}, <<"abc">>},
		   dec_entity_id(<<1,2,3,2,"abc">>)),
     ?_assertEqual({participant, <<"abc">>},
		   dec_entity_id(<<0,0,1,16#c1,"abc">>)),
     ?_assertEqual({'SEDPbuiltinTopicWriter', <<"abc">>},
		   dec_entity_id(<<0,0,2,16#c2,"abc">>)),
     ?_assertEqual({'SEDPbuiltinTopicReader', <<"abc">>},
		   dec_entity_id(<<0,0,2,16#c7,"abc">>)),
     ?_assertEqual({'SEDPbuiltinPublicationsWriter', <<"abc">>},
		   dec_entity_id(<<0,0,3,16#c2,"abc">>)),
     ?_assertEqual({'SEDPbuiltinPublicationsReader', <<"abc">>},
		   dec_entity_id(<<0,0,3,16#c7,"abc">>)),
     ?_assertEqual({'SEDPbuiltinSubscriptionsWriter', <<"abc">>},
		   dec_entity_id(<<0,0,4,16#c2,"abc">>)),
     ?_assertEqual({'SEDPbuiltinSubscriptionsReader', <<"abc">>},
		   dec_entity_id(<<0,0,4,16#c7,"abc">>)),
     ?_assertEqual({'SPDPbuiltinParticipantWriter', <<"abc">>},
		   dec_entity_id(<<0,1,0,16#c2,"abc">>)),
     ?_assertEqual({'SPDPbuiltinParticipantReader', <<"abc">>},
		   dec_entity_id(<<0,1,0,16#c7,"abc">>)),
     ?_assertEqual({'BuiltinParticipantMessageWriter', <<"abc">>},
		   dec_entity_id(<<0,2,0,16#c2,"abc">>)),
     ?_assertEqual({'BuiltinParticipantMessageReader', <<"abc">>},
		   dec_entity_id(<<0,2,0,16#c7,"abc">>)),
     ?_assertEqual({unknown, <<"abc">>},
		   dec_entity_id(<<0,0,0,0,"abc">>))].

enc_vendor_test_() ->
    [?_assertEqual(<<1,2>>, enc_vendor(<<1,2>>, <<>>)),
     ?_assertEqual(<<0,0>>, enc_vendor(unknown, <<>>))].

dec_vendor_test_() ->
    [?_assertEqual({<<1,2>>, <<"abc">>}, dec_vendor(<<1,2,"abc">>)),
     ?_assertEqual({unknown, <<"abc">>}, dec_vendor(<<0,0,"abc">>))].

enc_prot_ver_test() ->
    ?assertEqual(<<1,2>>, enc_prot_ver(#protocol_version{major=1,minor=2}, <<>>)).

dec_prot_ver_test() ->
    ?assertEqual({#protocol_version{major = 1, minor = 2}, <<"abc">>},
		 dec_prot_ver(<<1,2,"abc">>)).

enc_sn_test_() ->
    [?_assertEqual(<<0,0,0,0,0,0,1,0>>, enc_sn(256, <<>>)),
     ?_assertEqual(<<255,255,255,255,0,0,0,0>>, enc_sn(unknown, <<>>))].

dec_sn_test_() ->
    [?_assertEqual({256, <<"abc">>}, dec_sn(big, <<0,0,0,0,0,0,1,0,"abc">>)),
     ?_assertEqual({256, <<"abc">>}, dec_sn(little, <<0,0,0,0,0,1,0,0,"abc">>)),
     ?_assertEqual({unknown, <<"abc">>}, dec_sn(big, <<255,255,255,255,0,0,0,0,"abc">>))].

enc_fn_test() ->
    ?_assertEqual(<<0,0,1,0>>, enc_fn(256, <<>>)).

dec_fn_test_() ->
    [?_assertEqual({256, <<"abc">>}, dec_fn(big, <<0,0,1,0,"abc">>)),
     ?_assertEqual({256, <<"abc">>}, dec_fn(little, <<0,1,0,0,"abc">>))].

set_to_bitmap_test() ->
    Base = 1, Set = [1,2,8], N = 8,
    ?assertEqual(<<1:1, 1:1, 0:1, 0:1, 0:1, 0:1, 0:1, 1:1, 0:24>>,
		 set_to_bitmap(Base, N, Set)).

bitmap_to_set_test_() ->
    Base = 1, Set = [1,2,8], N = 8,
    [?_assertEqual(Set,
		   bitmap_to_set(big, Base, N, <<1:1, 1:1, 0:1, 0:1, 0:1, 0:1, 0:1, 1:1,
						 0:24>>)),
     ?_assertEqual(Set,
		   bitmap_to_set(little, Base, N, <<0:24,
						    1:1, 1:1, 0:1, 0:1, 0:1, 0:1, 0:1, 1:1>>))].

enc_snset_test() ->
    Base = 1, Set = [1,2,8], N = 8,
    ?assertEqual(<<0,0,0,0,0,0,0,1, 0,0,0,8, 1:1, 1:1, 0:1, 0:1, 0:1, 0:1, 0:1, 1:1, 0:24>>,
		 enc_snset(#sequence_number_set{base = Base, set = Set, num_bits = N}, <<>>)).

dec_snset_test_() ->
    Base = 1, Set = [1,2,8], N = 8,
    [?_assertEqual({#sequence_number_set{base = Base, set = Set, num_bits = N}, <<"abc">>},
		   dec_snset(big, <<0,0,0,0,0,0,0,1, 0,0,0,8, 1:1, 1:1, 0:1, 0:1, 0:1, 0:1, 0:1, 1:1, 0:24, "abc">>)),
     ?_assertEqual({#sequence_number_set{base = Base, set = Set, num_bits = N}, <<"abc">>},
		   dec_snset(little, <<0,0,0,0,1,0,0,0, 8,0,0,0, 0:24, 1:1, 1:1, 0:1, 0:1, 0:1, 0:1, 0:1, 1:1, "abc">>))].

enc_fnset_test() ->
    Base = 1, Set = [1,2,8], N = 8,
    ?assertEqual(<<0,0,0,1, 0,0,0,8, 1:1, 1:1, 0:1, 0:1, 0:1, 0:1, 0:1, 1:1, 0:24>>,
		 enc_fnset(#fragment_number_set{base = Base, set = Set, num_bits = N}, <<>>)).

dec_fnset_test_() ->
    Base = 1, Set = [1,2,8], N = 8,
    [?_assertEqual({#fragment_number_set{base = Base, set = Set, num_bits = N}, <<"abc">>},
		   dec_fnset(big, <<0,0,0,1, 0,0,0,8, 1:1, 1:1, 0:1, 0:1, 0:1, 0:1, 0:1, 1:1, 0:24, "abc">>)),
     ?_assertEqual({#fragment_number_set{base = Base, set = Set, num_bits = N}, <<"abc">>},
		   dec_fnset(little, <<1,0,0,0, 8,0,0,0, 0:24, 1:1, 1:1, 0:1, 0:1, 0:1, 0:1, 0:1, 1:1, "abc">>))].

enc_ts_test_() ->
    [?_assertEqual(<<0,0,0,0,0,0,0,0>>, enc_ts(zero, <<>>)),
     ?_assertEqual(<<255,255,255,255,255,255,255,255>>, enc_ts(invalid, <<>>)),
     ?_assertEqual(<<127,255,255,255,255,255,255,255>>, enc_ts(infinity, <<>>)),
     ?_assertEqual(<<0,0,0,1,0,0,0,2>>, enc_ts(#time{seconds = 1, fraction = 2}, <<>>))].

dec_ts_test_() ->
    [?_assertEqual({zero, <<"abc">>}, dec_ts(big, <<0,0,0,0,0,0,0,0,"abc">>)),
     ?_assertEqual({invalid, <<"abc">>}, dec_ts(big, <<255,255,255,255,255,255,255,255,"abc">>)),
     ?_assertEqual({infinity, <<"abc">>}, dec_ts(big, <<127,255,255,255,255,255,255,255,"abc">>)),
     ?_assertEqual({infinity, <<"abc">>}, dec_ts(little, <<255,255,255,127,255,255,255,255,"abc">>))
    ].

enc_loc_test_() ->
    {ok, IPv4Address} = inet:parse_ipv4_address("192.168.1.3"),
    {ok, IPv6Address} = inet:parse_ipv6_address("::3"),
    Kind = udpv6, Port = 2,
    Loc = #locator{kind = Kind, port = Port, address = IPv6Address},
    [?_assertEqual(<<0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3>>,
		   enc_loc(#locator{kind = udpv6, port = Port, address = IPv6Address}, <<>>)),
     ?_assertEqual(<<0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,192,168,1,3>>,
		   enc_loc(#locator{kind = udpv4, port = Port, address = IPv4Address}, <<>>)),
     ?_assertEqual(<<255,255,255,255,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3>>,
		   enc_loc(Loc#locator{kind = invalid,
				       address = <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3>>}, <<>>)),
     ?_assertEqual(<<0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3>>,
		   enc_loc(Loc#locator{port = invalid}, <<>>)),
     ?_assertEqual(<<0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>,
		   enc_loc(Loc#locator{address = invalid}, <<>>)),
     ?_assertEqual(<<255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>,
		   enc_loc(invalid, <<>>))].

dec_loc_test_() ->
    {ok, IPv4Address} = inet:parse_ipv4_address("192.168.1.3"),
    {ok, IPv6Address} = inet:parse_ipv6_address("::3"),
    Kind = udpv6, Port = 2, Address = <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3>>,
    Loc = #locator{kind = Kind, port = Port, address = Address},
    [?_assertEqual({#locator{kind = udpv6, port = Port, address = IPv6Address}, <<"abc">>},
		   dec_loc(big, <<0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,"abc">>)),
     ?_assertEqual({#locator{kind = udpv6, port = Port, address = IPv6Address}, <<"abc">>},
		   dec_loc(little, <<2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,"abc">>)),
     ?_assertEqual({#locator{kind = udpv4, port = Port, address = IPv4Address}, <<"abc">>},
		   dec_loc(big, <<0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,192,168,1,3,"abc">>)),
     ?_assertEqual({#locator{kind = udpv4, port = Port, address = IPv4Address}, <<"abc">>},
		   dec_loc(little, <<1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,168,1,3,"abc">>)),
     ?_assertEqual({Loc#locator{kind = invalid}, <<"abc">>},
		   dec_loc(big, <<255,255,255,255,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,"abc">>)),
     ?_assertEqual({Loc#locator{kind = invalid}, <<"abc">>},
		  dec_loc(little, <<255,255,255,255,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,"abc">>)),
     ?_assertEqual({Loc#locator{port = invalid, address = IPv6Address}, <<"abc">>},
		  dec_loc(big, <<0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,"abc">>)),
     ?_assertEqual({Loc#locator{port = invalid, address = IPv6Address}, <<"abc">>},
		  dec_loc(little, <<2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,"abc">>)),
     ?_assertEqual({Loc#locator{address = invalid}, <<"abc">>},
		  dec_loc(big, <<0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"abc">>)),
     ?_assertEqual({Loc#locator{address = invalid}, <<"abc">>},
		  dec_loc(little, <<2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"abc">>)),
     ?_assertEqual({invalid, <<"abc">>},
		  dec_loc(big, <<255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"abc">>))
    ].

enc_locl_test() ->
    {ok, IPv4Address} = inet:parse_ipv4_address("192.168.1.3"),
    Loc1 = #locator{kind = udpv4, port = 22, address = IPv4Address},
    {ok, IPv6Address} = inet:parse_ipv6_address("::3"),
    Loc2 = #locator{kind = udpv6, port = 2222, address = IPv6Address},
    BLoc1 = enc_loc(Loc1, <<>>), 
    BLoc2 = enc_loc(Loc2, <<>>), 
    BLocs = << <<BLoc/binary>> || BLoc <- [BLoc1, BLoc2] >>,
    ?assertEqual(<<0,0,0,2,BLocs/binary>>, enc_locl([Loc1, Loc2], <<>>)).

dec_locl_test() ->
    {ok, IPv4Address} = inet:parse_ipv4_address("192.168.1.3"),
    Loc1 = #locator{kind = udpv4, port = 22, address = IPv4Address},
    {ok, IPv6Address} = inet:parse_ipv6_address("::3"),
    Loc2 = #locator{kind = udpv6, port = 2222, address = IPv6Address},
    BLoc1 = enc_loc(Loc1, <<>>), 
    BLoc2 = enc_loc(Loc2, <<>>), 
    BLocs = << <<BLoc/binary>> || BLoc <- [BLoc1, BLoc2] >>,
    %% We can't test little in this set up.
    ?assertEqual({[Loc2, Loc1], <<>>}, dec_locl(big, <<0,0,0,2,BLocs/binary>>)).

enc_locudpv4_test() ->
    {ok, Address} = inet:parse_ipv4_address("0.0.0.2"),
    Loc = #locator_udpv4{port = 1, address = Address},
    ?assertEqual(<<0,0,0,2, 0,0,0,1>>, enc_locudpv4(Loc, <<>>)).

dec_locudpv4_test_() ->
    {ok, Address} = inet:parse_ipv4_address("0.0.0.2"),
    Loc = #locator_udpv4{port = 1, address = Address},
    [?_assertEqual({Loc, <<"abc">>}, dec_locudpv4(big, <<0,0,0,2, 0,0,0,1, "abc">>)),
     ?_assertEqual({Loc, <<"abc">>}, dec_locudpv4(little, <<2,0,0,0, 1,0,0,0, "abc">>))].

%% Submessages tests

acknack_test_() ->
    Reader_id = #entity_id{type = user, kind = unknown, key = <<1,2,3>>},
    Writer_id = #entity_id{type = user, kind = unknown, key = <<4,5,6>>},
    Submsg = #acknack{final_flag = false, reader_id = Reader_id, writer_id = Writer_id, 
		      reader_sn_state = #sequence_number_set{base = 3, set = [4,5], 
							     num_bits = 8}, 
		      count = 6},
    Id = 6, Flags = 0, Length = 28,
    Bin = <<1,2,3,0, 4,5,6,0, 0,0,0,0,0,0,0,3,0,0,0,8,96,0,0,0,0,0,0,6>>,
    [?_assertMatch(#submsg{id = Id, flags = Flags, length = Length, content = Bin},
		   enc_submsg(Submsg)),
     ?_assertEqual(Submsg, dec_submsg(Id, big, <<Flags>>, Bin))].

data_test_() ->
    Reader_id = #entity_id{type = user, kind = unknown, key = <<1,2,3>>},
    Writer_id = #entity_id{type = user, kind = unknown, key = <<4,5,6>>},
    Submsg = #data_msg{extra_flags = <<0,0>>, reader_id = Reader_id, writer_id = Writer_id, 
		       writer_sn = 3, inline_qos = undefined, endianness = big},
    Id = 21, Flags = 0, Length = 20,
    Bin = <<0,0,0,16,1,2,3,0,4,5,6,0,0,0,0,0,0,0,0,3>>,

    Value = <<5>>, Value_padded = <<Value/binary, 0:24>>,
    Param = #parameter{id = time_based_filter, value = <<5>>},
    Param_padded = Param#parameter{value = Value_padded},
    Submsg1 = Submsg#data_msg{inline_qos = [Param]},
    Submsg1_padded = Submsg#data_msg{inline_qos = [Param_padded]},
    Flags1 = 2, Length1 = 32,
    Bin1 = <<0,0,0,16,1,2,3,0,4,5,6,0,0,0,0,0,0,0,0,3,0,4,0,4,5,0,0,0,0,1,0,0>>,

    Submsg2 = Submsg#data_msg{serialized_data = <<7>>},
    Submsg2_padded = Submsg#data_msg{serialized_data = <<7, 0:24>>},
    Flags2 = 4, Length2 = 24,
    Bin2 = <<0,0,0,16,1,2,3,0,4,5,6,0,0,0,0,0,0,0,0,3,7,0,0,0>>,

    Submsg3 = Submsg#data_msg{serialized_key = <<8>>},
    Submsg3_padded = Submsg#data_msg{serialized_key = <<8, 0:24>>},
    Flags3 = 8, Length3 = 24,
    Bin3 = <<0,0,0,16,1,2,3,0,4,5,6,0,0,0,0,0,0,0,0,3,8,0,0,0>>,

    [?_assertEqual(#submsg{id = Id, flags = Flags, length = Length, content = Bin}, enc_submsg(Submsg)),
     ?_assertEqual(Submsg, dec_submsg(Id, big, <<Flags>>, Bin)),
     ?_assertEqual(#submsg{id = Id, flags = Flags1, length = Length1, content = Bin1}, enc_submsg(Submsg1)),
     ?_assertEqual(Submsg1_padded, dec_submsg(Id, big, <<Flags1>>, Bin1)),
     ?_assertEqual(#submsg{id = Id, flags = Flags2, length = Length2, content = Bin2}, enc_submsg(Submsg2)),
     ?_assertEqual(Submsg2_padded, dec_submsg(Id, big, <<Flags2>>, Bin2)),
     ?_assertEqual(#submsg{id = Id, flags = Flags3, length = Length3, content = Bin3}, enc_submsg(Submsg3)),
     ?_assertEqual(Submsg3_padded, dec_submsg(Id, big, <<Flags3>>, Bin3))].

data_frag_test_() ->
    Reader_id = #entity_id{type = user, kind = unknown, key = <<1,2,3>>},
    Writer_id = #entity_id{type = user, kind = unknown, key = <<4,5,6>>},
    Submsg = #data_frag{key_flag = false, extra_flags = <<0,0>>, reader_id = Reader_id, 
			writer_id = Writer_id, writer_sn = 3, fragment_starting_num = 4, 
			fragments_in_submessage = 5, sample_size = 6, inline_qos = undefined,
			serialized_payload = <<>>, endianness = big},
    Id = 22, Flags = 0, Length = 32,
    Bin = <<0,0,0,28,1,2,3,0,4,5,6,0,0,0,0,0,0,0,0,3,0,0,0,4,0,5,0,0,0,0,0,6>>,

    Submsg1 = Submsg#data_frag{key_flag = true},
    Flags1 = 4, Length1 = Length,
    Bin1 = Bin,

    Submsg2 = Submsg#data_frag{serialized_payload = <<"abc">>},
    Flags2 = 0, Length2 = 36,
    Bin2 = <<0,0,0,28,1,2,3,0,4,5,6,0,0,0,0,0,0,0,0,3,0,0,0,4,0,5,0,3,0,0,0,6,97,98,99,0>>,
    Submsg3 = Submsg2#data_frag{key_flag = true},
    Flags3 = 4, Length3 = Length2,
    Bin3 = Bin2,

    Value = <<5>>, Value_padded = <<Value/binary, 0:24>>,
    Param = #parameter{id = time_based_filter, value = <<5>>},
    Param_padded = Param#parameter{value = Value_padded},
    Submsg4 = Submsg#data_frag{inline_qos = [Param]},
    Submsg4_padded = Submsg#data_frag{inline_qos = [Param_padded]},
    Flags4 = 2, Length4 = 44,
    Bin4 = <<0,0,0,28,1,2,3,0,4,5,6,0,0,0,0,0,0,0,0,3,0,0,0,4,0,5,0,0,0,0,0,6,0,4,0,4,5,0,0,0,0,1,0,0>>,

    [?_assertMatch(#submsg{id = Id, flags = Flags, length = Length, content = Bin},
		   enc_submsg(Submsg)),
     ?_assertEqual(Submsg, dec_submsg(Id, big, <<Flags>>, Bin)),
     ?_assertMatch(#submsg{id = Id, flags = Flags1, length = Length1, content = Bin1},
		   enc_submsg(Submsg1)),
     ?_assertEqual(Submsg1, dec_submsg(Id, big, <<Flags1>>, Bin1)),
     ?_assertMatch(#submsg{id = Id, flags = Flags2, length = Length2, content = Bin2},
		   enc_submsg(Submsg2)),
     ?_assertEqual(Submsg2, dec_submsg(Id, big, <<Flags2>>, Bin2)),
     ?_assertMatch(#submsg{id = Id, flags = Flags3, length = Length3, content = Bin3},
		   enc_submsg(Submsg3)),
     ?_assertEqual(Submsg3, dec_submsg(Id, big, <<Flags3>>, Bin3)),
     ?_assertMatch(#submsg{id = Id, flags = Flags4, length = Length4, content = Bin4},
		   enc_submsg(Submsg4)),
     ?_assertEqual(Submsg4_padded, dec_submsg(Id, big, <<Flags4>>, Bin4))].

gap_test_() ->
    Reader_id = #entity_id{type = user, kind = unknown, key = <<1,2,3>>},
    Writer_id = #entity_id{type = user, kind = unknown, key = <<4,5,6>>},
    Submsg = #gap{reader_id = Reader_id, writer_id = Writer_id, gap_start = 3, 
		  gap_list = #sequence_number_set{base = 4, set = [5,6], num_bits = 8}},
    Id = 8, Flags = 0, Length = 32,
    Bin = <<1,2,3,0,4,5,6,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,4,0,0,0,8,96,0,0,0>>,
    [?_assertEqual(#submsg{id = Id, flags = Flags, length = Length, content = Bin}, enc_submsg(Submsg)),
     ?_assertEqual(Submsg, dec_submsg(Id, big, <<Flags>>, Bin))].

heartbeat_test_() ->    
    Reader_id = #entity_id{type = user, kind = unknown, key = <<1,2,3>>},
    Writer_id = #entity_id{type = user, kind = unknown, key = <<4,5,6>>},
    Submsg = #heartbeat{final_flag = false, liveliness_flag = false, 
			reader_id = Reader_id, writer_id = Writer_id, 
			first_sn = 3, last_sn = 4, count = 5},
    Id = 7, Flags = 0, Length = 28,
    Bin = <<1,2,3,0,4,5,6,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,4,0,0,0,5>>,

    Submsg1 = Submsg#heartbeat{final_flag = true},
    Flags1 = 2,
    Submsg2 = Submsg#heartbeat{liveliness_flag = true},
    Flags2 = 4,
    [?_assertMatch(#submsg{id = Id, flags = Flags, length = Length, content = Bin},
		   enc_submsg(Submsg)),
     ?_assertEqual(Submsg, dec_submsg(Id, big, <<Flags>>, Bin)),
     ?_assertMatch(#submsg{id = Id, flags = Flags1, length = Length, content = Bin},
		   enc_submsg(Submsg1)),
     ?_assertEqual(Submsg1, dec_submsg(Id, big, <<Flags1>>, Bin)),
     ?_assertMatch(#submsg{id = Id, flags = Flags2, length = Length, content = Bin},
		   enc_submsg(Submsg2)),
     ?_assertEqual(Submsg2, dec_submsg(Id, big, <<Flags2>>, Bin))].

heartbeat_frag_test_() ->
    Reader_id = #entity_id{type = user, kind = unknown, key = <<1,2,3>>},
    Writer_id = #entity_id{type = user, kind = unknown, key = <<4,5,6>>},
    Submsg = #heartbeat_frag{reader_id = Reader_id, writer_id = Writer_id, writer_sn = 3, 
			     last_fragment_num = 4, count = 5},
    Id = 19, Flags = 0, Length = 24,
    Bin = <<1,2,3,0,4,5,6,0,0,0,0,0,0,0,0,3,0,0,0,4,0,0,0,5>>,
    [?_assertMatch(#submsg{id = Id, flags = Flags, length = Length, content = Bin},
		   enc_submsg(Submsg)),
     ?_assertEqual(Submsg, dec_submsg(Id, big, <<Flags>>, Bin))].

info_destination_test_() ->
    Submsg = #info_destination{guid_prefix = <<0,0,0,0,0,0,0,0,0,0,0,1>>},
    Id = 14, Flags = 0, Length = 12,
    Bin = <<0,0,0,0,0,0,0,0,0,0,0,1>>,
    [?_assertMatch(#submsg{id = Id, flags = Flags, length = Length, content = Bin},
		   enc_submsg(Submsg)),
     ?_assertEqual(Submsg, dec_submsg(Id, big, <<Flags>>, Bin))].

info_reply_test_() ->
    {ok, IPv6Address} = inet:parse_ipv6_address("::3"),
    Locator = #locator{kind = udpv6, port = 2, address = IPv6Address},
    Submsg = #info_reply{unicast_locator_list = [Locator]}, 
    Id = 15, Flags = 0, Length = 28,
    Bin = <<0,0,0,1,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3>>,

    {ok, IPv6Address1} = inet:parse_ipv6_address("::6"),
    Locator1 = #locator{kind = udpv6, port = 5, address = IPv6Address1},
    Submsg1 = Submsg#info_reply{multicast_locator_list = [Locator1]}, 
    Flags1 = 2, Length1 = 56,
    Bin1 = <<0,0,0,1,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,
	     0,0,0,1,0,0,0,2,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6>>,
    [?_assertMatch(#submsg{id = Id, flags = Flags, length = Length, content = Bin},
		   enc_submsg(Submsg)),
     ?_assertEqual(Submsg#info_reply{multicast_locator_list = []},
		   dec_submsg(Id, big, <<Flags>>, Bin)),
     ?_assertMatch(#submsg{id = Id, flags = Flags1, length = Length1, content = Bin1},
		   enc_submsg(Submsg1)),
     ?_assertEqual(Submsg1, dec_submsg(Id, big, <<Flags1>>, Bin1))].

info_reply_ip4_test_() ->
    {ok, Address} = inet:parse_ipv4_address("0.0.0.2"),
    Locator = #locator_udpv4{port = 1, address = Address},
    Submsg = #info_reply_ip4{unicast_locator = Locator},
    Id = 13, Flags = 0, Length = 8,
    Bin = <<0,0,0,2,0,0,0,1>>,

    {ok, Address1} = inet:parse_ipv4_address("0.0.0.4"),
    Locator1 = #locator_udpv4{port = 3, address = Address1},
    Submsg1 = Submsg#info_reply_ip4{multicast_locator = Locator1},
    Flags1 = 2, Length1 = 16,
    Bin1 = <<0,0,0,2,0,0,0,1,0,0,0,4,0,0,0,3>>,
    [?_assertMatch(#submsg{id = Id, flags = Flags, length = Length, content = Bin},
		   enc_submsg(Submsg)),
     ?_assertEqual(Submsg, dec_submsg(Id, big, <<Flags>>, Bin)),
     ?_assertMatch(#submsg{id = Id, flags = Flags1, length = Length1, content = Bin1},
		   enc_submsg(Submsg1)),
     ?_assertEqual(Submsg1, dec_submsg(Id, big, <<Flags1>>, Bin1))].

info_source_test_() ->
    Submsg = #info_source{protocol_version = #protocol_version{major = 1, minor = 2}, 
			  vendor_id = <<3,4>>, guid_prefix = <<0,0,0,0,0,0,0,0,0,0,0,5>>},
    Id = 12, Flags = 0, Length = 20,
    Bin = <<0,0,0,0,1,2,3,4,0,0,0,0,0,0,0,0,0,0,0,5>>,
    [?_assertMatch(#submsg{id = Id, flags = Flags, length = Length, content = Bin},
		   enc_submsg(Submsg)),
     ?_assertEqual(Submsg, dec_submsg(Id, big, <<Flags>>, Bin))].

info_timestamp_test_() ->
    Submsg = #info_timestamp{},
    Id = 9, Flags = 2, Length = 0,
    Bin = <<>>,

    Timestamp = #time{seconds = 1, fraction = 2},
    Submsg1 = Submsg#info_timestamp{timestamp = Timestamp},
    Flags1 = 0, Length1 = 8,
    Bin1 = <<0,0,0,1,0,0,0,2>>,
    [?_assertMatch(#submsg{id = Id, flags = Flags, length = Length, content = Bin},
		   enc_submsg(Submsg)),
     ?_assertEqual(Submsg, dec_submsg(Id, big, <<Flags>>, Bin)),
     ?_assertMatch(#submsg{id = Id, flags = Flags1, length = Length1, content = Bin1},
		   enc_submsg(Submsg1)),
     ?_assertEqual(Submsg1, dec_submsg(Id, big, <<Flags1>>, Bin1))].

nack_frag_test_() ->
    Reader_id = #entity_id{type = user, kind = unknown, key = <<1,2,3>>},
    Writer_id = #entity_id{type = user, kind = unknown, key = <<4,5,6>>},
    Submsg = #nack_frag{reader_id = Reader_id, writer_id = Writer_id, writer_sn = 3,
			fragment_number_state = #fragment_number_set{base = 4, 
								     set = [5,6], 
								     num_bits = 8}, 
			count = 7},
    Id = 18, Flags = 0, Length = 32,
    Bin = <<1,2,3,0,4,5,6,0,0,0,0,0,0,0,0,3,0,0,0,4,0,0,0,8,96,0,0,0,0,0,0,7>>,
    [?_assertMatch(#submsg{id = Id, flags = Flags, length = Length, content = Bin},
		   enc_submsg(Submsg)),
     ?_assertEqual(Submsg, dec_submsg(Id, big, <<Flags>>, Bin))].

pad_test_() ->
    Submsg = #pad{},
    Id = 1, Flags = 0, Length = 0,
    Bin = <<>>,
    [?_assertMatch(#submsg{id = Id, flags = Flags, length = Length, content = Bin},
		   enc_submsg(Submsg)),
     ?_assertEqual(Submsg, dec_submsg(Id, big, <<Flags>>, Bin))].

%% TODO: Add tests for submessages with unknown Id's. These should be silently ignored.

enc_header_test_() ->
    Header = #header{protocol_version = #protocol_version{major = 1, minor = 2}, 
		     vendor_id = <<3,4>>, guid_prefix = <<0,0,0,0,0,0,0,0,0,0,0,5>>},
    Bin = <<$R,$T,$P,$S,1,2,3,4,0,0,0,0,0,0,0,0,0,0,0,5>>,
    [?_assertEqual(Bin, enc_header(Header)),
     ?_assertEqual(Header, dec_header(Bin))].

%% Convenience function for turning internal messages
%% representation into the external one. (not exported).
enc_message(#message{header = Header, submessages = Submessages}) ->
    enc_message(enc_header(Header), [enc_submsg(Submsg) || Submsg <- Submessages]).

enc_message_test() ->
    Header = #header{protocol_version = #protocol_version{major = 1, minor = 2}, 
		     vendor_id = <<3, 4>>, guid_prefix = <<0,0,0,0,0,0,0,0,0,0,0,5>>},
    Msg = #message{header = Header, submessages = []},
    ?assertError(_, enc_message(Msg)).

enc_message_pad_test_() ->
    Header = #header{protocol_version = #protocol_version{major = 1, minor = 2}, 
		     vendor_id = <<3, 4>>, guid_prefix = <<0,0,0,0,0,0,0,0,0,0,0,5>>},
    Submsg = #pad{},
    Msg = #message{header = Header, submessages = [Submsg]},
    Bin = <<$R,$T,$P,$S,1,2,3,4,0,0,0,0,0,0,0,0,0,0,0,5,1,0,0,0>>,
    [?_assertEqual(Bin, enc_message(Msg)),
     ?_assertEqual(Msg, dec_message(Bin))].

enc_message_data_test_() ->
    Reader_id = #entity_id{type = user, kind = unknown, key = <<1,2,3>>},
    Writer_id = #entity_id{type = user, kind = unknown, key = <<4,5,6>>},
    Header = #header{protocol_version = #protocol_version{major = 1, minor = 2}, 
		     vendor_id = <<3,4>>, guid_prefix = <<0,0,0,0,0,0,0,0,0,0,0,5>>},
    Serialized_data = <<"Hello, World!">>,
    Serialized_data_padded = <<Serialized_data/binary, 0, 0, 0>>,
    Submsg = #data_msg{extra_flags = <<0,0>>, reader_id = Reader_id, writer_id = Writer_id, 
		       writer_sn = 3, inline_qos = undefined, serialized_data = Serialized_data,
		       endianness = big},
    Submsg_padded = Submsg#data_msg{serialized_data = Serialized_data_padded},
    Msg = #message{header = Header, submessages = [Submsg]},
    Bin = <<$R,$T,$P,$S,1,2,3,4,0,0,0,0,0,0,0,0,0,0,0,5,21,4,0,36,0,0,0,16,1,2,3,0,4,5,
	    6,0,0,0,0,0,0,0,0,3,72,101,108,108,111,44,32,87,111,114,108,100,33,0,0,0>>,
    [?_assertEqual(Bin, enc_message(Msg)),
     ?_assertEqual(Msg#message{submessages = [Submsg_padded]}, dec_message(Bin))].

enc_message_data_small_test() ->
    Reader_id = #entity_id{type = user, kind = unknown, key = <<1,2,3>>},
    Writer_id = #entity_id{type = user, kind = unknown, key = <<4,5,6>>},
    Header = #header{protocol_version = #protocol_version{major = 1, minor = 2}, 
		     vendor_id = <<3,4>>, guid_prefix = <<0,0,0,0,0,0,0,0,0,0,0,5>>},
    %% TODO: Use a differernt random number generator. Does not have
    %% to be strong but the non-strong one is depreciated.
    Serialized_data = crypto:strong_rand_bytes(128),
    Submsg = #data_msg{extra_flags = <<0,0>>, reader_id = Reader_id, writer_id = Writer_id, 
		       writer_sn = 3, inline_qos = undefined, serialized_data = Serialized_data,
		       endianness = big},
    Msg = #message{header = Header, submessages = [Submsg, Submsg, Submsg, Submsg]},
    Bin = enc_message(Msg),
    ?assertEqual(Msg, dec_message(Bin)).

enc_message_data_big_test() ->
    Reader_id = #entity_id{type = user, kind = unknown, key = <<1,2,3>>},
    Writer_id = #entity_id{type = user, kind = unknown, key = <<4,5,6>>},
    Header = #header{protocol_version = #protocol_version{major = 1, minor = 2}, 
		     vendor_id = <<3,4>>, guid_prefix = <<0,0,0,0,0,0,0,0,0,0,0,5>>},
    Serialized_data = <<0:500/unit:32>>, % Mind the padding!!
    Submsg = #data_msg{extra_flags = <<0,0>>, reader_id = Reader_id, writer_id = Writer_id, 
		       writer_sn = 3, inline_qos = [], serialized_data = Serialized_data,
		       endianness = big},
    Msg = #message{header = Header, submessages = [Submsg, Submsg]},
    Bin = enc_message(Msg),
    ?assertEqual(Msg, dec_message(Bin)).

enc_message_data_bigger_test() ->
    Reader_id = #entity_id{type = user, kind = unknown, key = <<1,2,3>>},
    Writer_id = #entity_id{type = user, kind = unknown, key = <<4,5,6>>},
    Header = #header{protocol_version = #protocol_version{major = 1, minor = 2}, 
		     vendor_id = <<3,4>>, guid_prefix = <<0,0,0,0,0,0,0,0,0,0,0,5>>},
    Serialized_data = <<0:16#ffff/unit:32>>,
    Submsg = #data_msg{extra_flags = <<0,0>>, reader_id = Reader_id, writer_id = Writer_id, 
		       writer_sn = 3, inline_qos = [], serialized_data = Serialized_data},
    Msg = #message{header = Header, submessages = [Submsg, #pad{}]},
    ?assertError(_, enc_message(Msg)).

-endif.
